PG-1095 Add format validation CI workflow and format sources (#308)

* PG-1095 Add code format validation to CI

* PG-1095 Manually fix some codestyle

* PG-1095 Add comment to pgindent_exclude file

* PG-1095 Format sources

* PG-1095 Fix makefile

* PG-1095 Remove accidentally commited file

* PG-1094 Update gitignore

* PG-1094 Update pgindent_exclude

* PG-1095 Update typedefs list

* PG-1095 Manual code formatting

* PG-1095 Update typedefs list

* PG-1095 More manual code formatting

* PG-1095 More manual code formatting

* PG-1095 More fixes

* PG-1095 Fix
pull/209/head
Artem Gavrilov 10 months ago committed by GitHub
parent 2f31c0ed45
commit d2237acde5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 57
      .github/workflows/check.yaml
  2. 4
      .gitignore
  3. 11
      Makefile.in
  4. 9
      pgindent_excludes
  5. 4
      src/access/pg_tde_ddl.c
  6. 10
      src/access/pg_tde_slot.c
  7. 76
      src/access/pg_tde_tdemap.c
  8. 1
      src/access/pg_tde_xlog.c
  9. 35
      src/access/pg_tde_xlog_encrypt.c
  10. 1
      src/catalog/tde_global_space.c
  11. 27
      src/catalog/tde_keyring.c
  12. 59
      src/catalog/tde_principal_key.c
  13. 24
      src/common/pg_tde_shmem.c
  14. 7
      src/common/pg_tde_utils.c
  15. 37
      src/encryption/enc_aes.c
  16. 64
      src/encryption/enc_tde.c
  17. 3
      src/include/catalog/tde_keyring.h
  18. 21
      src/include/common/pg_tde_shmem.h
  19. 5
      src/include/keyring/keyring_curl.h
  20. 2
      src/include/keyring/keyring_vault.h
  21. 8
      src/include/pg_tde_defines.h
  22. 12
      src/keyring/keyring_api.c
  23. 55
      src/keyring/keyring_curl.c
  24. 3
      src/keyring/keyring_file.c
  25. 26
      src/keyring/keyring_vault.c
  26. 19
      src/pg_tde.c
  27. 8
      src/pg_tde_event_capture.c
  28. 61
      src/smgr/pg_tde_smgr.c
  29. 4
      src/transam/pg_tde_xact_handler.c
  30. 113
      typedefs.list

@ -0,0 +1,57 @@
name: Checks
on:
pull_request:
jobs:
format:
name: Format
runs-on: ubuntu-22.04
timeout-minutes: 5
steps:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y libcurl4-openssl-dev
- name: Clone postgres repository
uses: actions/checkout@v4
with:
repository: 'postgres/postgres'
ref: 'REL_17_STABLE'
- name: Checkout sources
uses: actions/checkout@v4
with:
path: 'contrib/pg_tde'
- name: Configure postgres
run: ./configure
- name: Configure pg_tde
run: ./configure
working-directory: contrib/pg_tde
- name: Install perltidy
run: sudo cpan -T SHANCOCK/Perl-Tidy-20230309.tar.gz
- name: Install pg_bsd_indent
working-directory: src/tools/pg_bsd_indent
run: sudo make install
- name: Add pg_bsd_indent and pgindent to path
run: |
echo "/usr/local/pgsql/bin" >> $GITHUB_PATH
echo "${{ github.workspace }}/src/tools/pgindent" >> $GITHUB_PATH
- name: Format sources
working-directory: contrib/pg_tde
run: |
make update-typedefs
make indent
- name: Check files are formatted and no source code changes
working-directory: contrib/pg_tde
run: |
git status
git diff --exit-code

4
.gitignore vendored

@ -9,3 +9,7 @@ __pycache__
/autom4te.cache
/configure~
t/results
src/include/config.h
# tools files
typedefs-full.list

@ -74,3 +74,14 @@ include $(top_srcdir)/contrib/contrib-global.mk
endif
override SHLIB_LINK += @tde_LDFLAGS@ -lcrypto -lssl
# Fetches typedefs list for PostgreSQL core and merges it with typedefs defined in this project.
# https://wiki.postgresql.org/wiki/Running_pgindent_on_non-core_code_or_development_code
update-typedefs:
wget -q -O - "https://buildfarm.postgresql.org/cgi-bin/typedefs.pl?branch=REL_17_STABLE" | cat - typedefs.list | sort | uniq > typedefs-full.list
# Indents projects sources.
indent:
pgindent --typedefs=typedefs-full.list --excludes=pgindent_excludes .
.PHONY: update-typedefs indent

@ -0,0 +1,9 @@
# List of filename patterns to exclude from pgindent runs
#
# This contains code copied from postgres tree as is and slightly modified.
# We don't want to run pgindent on these files to avoid unnecessary conflicts.
src\d\d/
# This file is generated by configure and should not be formatted
src/include/config.h

@ -22,7 +22,8 @@ static object_access_hook_type prev_object_access_hook = NULL;
static void tdeheap_object_access_hook(ObjectAccessType access, Oid classId,
Oid objectId, int subId, void *arg);
void SetupTdeDDLHooks(void)
void
SetupTdeDDLHooks(void)
{
prev_object_access_hook = object_access_hook;
object_access_hook = tdeheap_object_access_hook;
@ -40,6 +41,7 @@ tdeheap_object_access_hook(ObjectAccessType access, Oid classId, Oid objectId,
if (access == OAT_DROP && classId == RelationRelationId)
{
ObjectAccessDrop *drop_arg = (ObjectAccessDrop *) arg;
rel = relation_open(objectId, AccessShareLock);
}
if (rel != NULL)

@ -49,6 +49,7 @@ static void
tdeheap_tts_buffer_heap_init(TupleTableSlot *slot)
{
TDEBufferHeapTupleTableSlot *bslot = (TDEBufferHeapTupleTableSlot *) slot;
bslot->cached_relation_key = NULL;
}
@ -254,6 +255,7 @@ static HeapTuple
tdeheap_tts_buffer_heap_get_heap_tuple(TupleTableSlot *slot)
{
BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
Assert(!TTS_EMPTY(slot));
if (!bslot->base.tuple)
@ -367,6 +369,7 @@ tdeheap_slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *of
uint32 off; /* offset in tuple data */
bits8 *bp = tup->t_bits; /* ptr to null bitmap in tuple */
bool slow; /* can we use/set attcacheoff? */
/* We can only fetch as many attributes as the tuple has. */
natts = Min(HeapTupleHeaderGetNatts(tuple->t_data), natts);
@ -465,7 +468,7 @@ slot_copytuple(void* buffer, HeapTuple tuple)
newTuple->t_self = tuple->t_self;
newTuple->t_tableOid = tuple->t_tableOid;
newTuple->t_data = (HeapTupleHeader) ((char *) newTuple + HEAPTUPLESIZE);
// We don't copy the data, it will be copied by the decryption code
/* We don't copy the data, it will be copied by the decryption code */
memcpy((char *) newTuple->t_data, (char *) tuple->t_data, tuple->t_data->t_hoff);
return newTuple;
}
@ -487,7 +490,8 @@ const TupleTableSlotOps TTSOpsTDEBufferHeapTuple = {
/* A buffer heap tuple table slot can not "own" a minimal tuple. */
.get_minimal_tuple = NULL,
.copy_heap_tuple = tdeheap_tts_buffer_heap_copy_heap_tuple,
.copy_minimal_tuple = tdeheap_tts_buffer_heap_copy_minimal_tuple};
.copy_minimal_tuple = tdeheap_tts_buffer_heap_copy_minimal_tuple
};
/* --------------------------------
* ExecStoreBufferHeapTuple
@ -516,6 +520,7 @@ PGTdeExecStoreBufferHeapTuple(Relation rel,
{
TDEBufferHeapTupleTableSlot *bslot = (TDEBufferHeapTupleTableSlot *) slot;
/*
* sanity checks
*/
@ -557,6 +562,7 @@ PGTdeExecStorePinnedBufferHeapTuple(Relation rel,
Buffer buffer)
{
TDEBufferHeapTupleTableSlot *bslot = (TDEBufferHeapTupleTableSlot *) slot;
/*
* sanity checks
*/

@ -231,6 +231,7 @@ RelKeyData *
tde_create_rel_key(RelFileNumber rel_num, InternalKey *key, TDEPrincipalKeyInfo *principal_key_info)
{
RelKeyData rel_key_data;
memcpy(&rel_key_data.principal_key_id, &principal_key_info->keyId, sizeof(TDEPrincipalKeyId));
memcpy(&rel_key_data.internal_key, key, sizeof(InternalKey));
rel_key_data.internal_key.ctx = NULL;
@ -238,6 +239,7 @@ tde_create_rel_key(RelFileNumber rel_num, InternalKey *key, TDEPrincipalKeyInfo
/* Add to the decrypted key to cache */
return pg_tde_put_key_into_cache(rel_num, &rel_key_data);
}
/*
* Encrypts a given key and returns the encrypted one.
*/
@ -373,15 +375,18 @@ pg_tde_write_map_entry(const RelFileLocator *rlocator, uint32 entry_type, char *
/*
* Read until we find an empty slot. Otherwise, read until end. This seems
* to be less frequent than vacuum. So let's keep this function here rather
* than overloading the vacuum process.
* to be less frequent than vacuum. So let's keep this function here
* rather than overloading the vacuum process.
*/
while (1)
{
prev_pos = curr_pos;
found = pg_tde_read_one_map_entry(map_fd, NULL, MAP_ENTRY_EMPTY, &map_entry, &curr_pos);
/* We either reach EOF or found an empty slot in the middle of the file */
/*
* We either reach EOF or found an empty slot in the middle of the
* file
*/
if (prev_pos == curr_pos || found)
break;
@ -389,7 +394,10 @@ pg_tde_write_map_entry(const RelFileLocator *rlocator, uint32 entry_type, char *
key_index++;
}
/* Write the given entry at the location pointed by prev_pos; i.e. the free entry */
/*
* Write the given entry at the location pointed by prev_pos; i.e. the
* free entry
*/
curr_pos = prev_pos;
pg_tde_write_one_map_entry(map_fd, rlocator, entry_type, key_index, &map_entry, &prev_pos);
@ -425,6 +433,7 @@ pg_tde_write_one_map_entry(int fd, const RelFileLocator *rlocator, uint32 flags,
if (bytes_written != MAP_ENTRY_SIZE)
{
char db_map_path[MAXPGPATH] = {0};
pg_tde_set_db_file_paths(rlocator->dbOid, rlocator->spcOid, db_map_path, NULL);
ereport(FATAL,
(errcode_for_file_access(),
@ -434,6 +443,7 @@ pg_tde_write_one_map_entry(int fd, const RelFileLocator *rlocator, uint32 flags,
if (pg_fsync(fd) != 0)
{
char db_map_path[MAXPGPATH] = {0};
pg_tde_set_db_file_paths(rlocator->dbOid, rlocator->spcOid, db_map_path, NULL);
ereport(data_sync_elevel(ERROR),
(errcode_for_file_access(),
@ -528,7 +538,8 @@ pg_tde_write_key_map_entry(const RelFileLocator *rlocator, RelKeyData *enc_rel_k
* Deletes a map entry by setting marking it as unused. We don't have to delete
* the actual key data as valid key data entries are identify by valid map entries.
*/
void pg_tde_delete_key_map_entry(const RelFileLocator *rlocator, uint32 key_type)
void
pg_tde_delete_key_map_entry(const RelFileLocator *rlocator, uint32 key_type)
{
int32 key_index = 0;
off_t offset = 0;
@ -575,7 +586,8 @@ void pg_tde_delete_key_map_entry(const RelFileLocator *rlocator, uint32 key_type
*
* A caller should hold an EXCLUSIVE tde_lwlock_enc_keys lock.
*/
void pg_tde_free_key_map_entry(const RelFileLocator *rlocator, uint32 key_type, off_t offset)
void
pg_tde_free_key_map_entry(const RelFileLocator *rlocator, uint32 key_type, off_t offset)
{
int32 key_index = 0;
char db_map_path[MAXPGPATH] = {0};
@ -621,7 +633,10 @@ void pg_tde_free_key_map_entry(const RelFileLocator *rlocator, uint32 key_type,
static File
keyrotation_init_file(TDEPrincipalKeyInfo *new_principal_key_info, char *rotated_filename, char *filename, bool *is_new_file, off_t *curr_pos)
{
/* Set the new filenames for the key rotation process - temporary at the moment */
/*
* Set the new filenames for the key rotation process - temporary at the
* moment
*/
snprintf(rotated_filename, MAXPGPATH, "%s.r", filename);
/* Create file, truncate if the rotate file already exits */
@ -677,11 +692,17 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p
principal_key->keyInfo.tablespaceId,
db_map_path, db_keydata_path);
/* Let's update the pathnames in the local variable for ease of use/readability */
/*
* Let's update the pathnames in the local variable for ease of
* use/readability
*/
strncpy(m_path[OLD_PRINCIPAL_KEY], db_map_path, MAXPGPATH);
strncpy(k_path[OLD_PRINCIPAL_KEY], db_keydata_path, MAXPGPATH);
/* Open both files in read only mode. We don't need to track the current position of the keydata file. We always use the key index */
/*
* Open both files in read only mode. We don't need to track the current
* position of the keydata file. We always use the key index
*/
m_fd[OLD_PRINCIPAL_KEY] = pg_tde_open_file(m_path[OLD_PRINCIPAL_KEY], &principal_key->keyInfo, false, O_RDONLY, &is_new_file, &curr_pos[OLD_PRINCIPAL_KEY]);
k_fd[OLD_PRINCIPAL_KEY] = pg_tde_open_file(k_path[OLD_PRINCIPAL_KEY], &principal_key->keyInfo, false, O_RDONLY, &is_new_file, &read_pos_tmp);
@ -691,7 +712,8 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p
/* Read all entries until EOF */
for (key_index[OLD_PRINCIPAL_KEY] = 0;; key_index[OLD_PRINCIPAL_KEY]++)
{
TDEMapEntry read_map_entry, write_map_entry;
TDEMapEntry read_map_entry,
write_map_entry;
RelFileLocator rloc;
prev_pos[OLD_PRINCIPAL_KEY] = curr_pos[OLD_PRINCIPAL_KEY];
@ -705,7 +727,10 @@ pg_tde_perform_rotate_key(TDEPrincipalKey *principal_key, TDEPrincipalKey *new_p
if (found == false)
continue;
/* Set the relNumber of rlocator. Ignore the tablespace Oid since we only place our files under the default. */
/*
* Set the relNumber of rlocator. Ignore the tablespace Oid since we
* only place our files under the default.
*/
rloc.relNumber = read_map_entry.relNumber;
rloc.dbOid = principal_key->keyInfo.databaseId;
rloc.spcOid = DEFAULTTABLESPACE_OID; /* TODO. Revisit */
@ -955,12 +980,11 @@ pg_tde_get_key_from_file(const RelFileLocator *rlocator, uint32 key_type, bool n
*
* We should hold the lock until the internal key is loaded to be sure the
* retrieved key was encrypted with the obtained principal key. Otherwise,
* the next may happen:
* - GetPrincipalKey returns key "PKey_1".
* - Some other process rotates the Principal key and re-encrypt an
* Internal key with "PKey_2".
* - We read the Internal key and decrypt it with "PKey_1" (that's what
* we've got). As the result we return an invalid Internal key.
* the next may happen: - GetPrincipalKey returns key "PKey_1". - Some
* other process rotates the Principal key and re-encrypt an Internal key
* with "PKey_2". - We read the Internal key and decrypt it with "PKey_1"
* (that's what we've got). As the result we return an invalid Internal
* key.
*/
LWLockAcquire(lock_pk, LW_SHARED);
principal_key = GetPrincipalKey(rlocator->dbOid, rlocator->spcOid, LW_SHARED);
@ -1031,8 +1055,8 @@ pg_tde_process_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *
Assert(offset);
/*
* Open and validate file for basic correctness. DO NOT create it.
* The file should pre-exist otherwise we should never be here.
* Open and validate file for basic correctness. DO NOT create it. The
* file should pre-exist otherwise we should never be here.
*/
map_fd = pg_tde_open_file(db_map_path, NULL, false, O_RDWR, &is_new_file, &curr_pos);
@ -1061,8 +1085,8 @@ pg_tde_process_map_entry(const RelFileLocator *rlocator, uint32 key_type, char *
/*
* Read until we find an empty slot. Otherwise, read until end. This seems
* to be less frequent than vacuum. So let's keep this function here rather
* than overloading the vacuum process.
* to be less frequent than vacuum. So let's keep this function here
* rather than overloading the vacuum process.
*/
while (1)
{
@ -1300,6 +1324,7 @@ pg_tde_read_one_keydata(int keydata_fd, int32 key_index, TDEPrincipalKey *princi
if ((read_pos + INTERNAL_KEY_DAT_LEN) > lseek(keydata_fd, 0, SEEK_END))
{
char db_keydata_path[MAXPGPATH] = {0};
pg_tde_set_db_file_paths(principal_key->keyInfo.databaseId, principal_key->keyInfo.tablespaceId, NULL, db_keydata_path);
ereport(FATAL,
(errcode(ERRCODE_NO_DATA_FOUND),
@ -1313,6 +1338,7 @@ pg_tde_read_one_keydata(int keydata_fd, int32 key_index, TDEPrincipalKey *princi
if (pg_pread(keydata_fd, &(enc_rel_key_data->internal_key), INTERNAL_KEY_DAT_LEN, read_pos) != INTERNAL_KEY_DAT_LEN)
{
char db_keydata_path[MAXPGPATH] = {0};
pg_tde_set_db_file_paths(principal_key->keyInfo.databaseId, principal_key->keyInfo.tablespaceId, NULL, db_keydata_path);
ereport(FATAL,
(errcode_for_file_access(),
@ -1356,7 +1382,10 @@ pg_tde_get_principal_key_info(Oid dbOid, Oid spcOid)
close(fd);
/* It's not a new file. So we can memcpy the principal key info from the header */
/*
* It's not a new file. So we can memcpy the principal key info from the
* header
*/
if (!is_new_file)
{
size_t sz = sizeof(TDEPrincipalKeyInfo);
@ -1479,7 +1508,8 @@ pg_tde_put_key_into_cache(RelFileNumber rel_num, RelKeyData *key)
tde_rel_key_cache->cap = pageSize / sizeof(RelKeyCacheRec);
}
/* Add another mem page if there is no more room left for another key. We
/*
* Add another mem page if there is no more room left for another key. We
* allocate `current_memory_size` + 1 page and copy data there.
*/
if (tde_rel_key_cache->len + 1 >

@ -65,6 +65,7 @@ tdeheap_rmgr_redo(XLogReaderState *record)
else if (info == XLOG_TDE_ADD_KEY_PROVIDER_KEY)
{
KeyringProviderXLRecord *xlrec = (KeyringProviderXLRecord *) XLogRecGetData(record);
redo_key_provider_info(xlrec);
}

@ -134,13 +134,13 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset)
#endif
/*
* Go through the buf page-by-page and encrypt them.
* We may start or finish writing from/in the middle of the page
* (walsender or `full_page_writes = off`). So preserve a page header
* for the IV init data.
* Go through the buf page-by-page and encrypt them. We may start or
* finish writing from/in the middle of the page (walsender or
* `full_page_writes = off`). So preserve a page header for the IV init
* data.
*
* TODO: check if walsender restarts form the beggining of the page
* in case of the crash.
* TODO: check if walsender restarts form the beggining of the page in
* case of the crash.
*/
for (enc_off = 0; enc_off < count;)
{
@ -151,8 +151,9 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset)
memcpy((char *) curr_page_hdr, (char *) buf + enc_off, SizeOfXLogShortPHD);
/*
* Need to use a separate buf for the encryption so the page remains non-crypted
* in the XLog buf (XLogInsert has to have access to records' lsn).
* Need to use a separate buf for the encryption so the page
* remains non-crypted in the XLog buf (XLogInsert has to have
* access to records' lsn).
*/
enc_buf_page = (XLogPageHeader) (TDEXLogEncryptBuf + enc_off);
memcpy((char *) enc_buf_page, (char *) buf + enc_off, (Size) XLogPageHeaderSize(curr_page_hdr));
@ -175,9 +176,9 @@ TDEXLogWriteEncryptedPages(int fd, const void *buf, size_t count, off_t offset)
}
/*
* The page is zeroed (no data), no sense to enctypt.
* This may happen when base_backup or other requests XLOG SWITCH and
* some pages in XLog buffer still not used.
* The page is zeroed (no data), no sense to enctypt. This may happen
* when base_backup or other requests XLOG SWITCH and some pages in
* XLog buffer still not used.
*/
if (curr_page_hdr->xlp_magic == 0)
{
@ -241,13 +242,13 @@ tdeheap_xlog_seg_read(int fd, void *buf, size_t count, off_t offset)
readsz = pg_pread(fd, buf, count, offset);
/*
* Read the buf page by page and decypt ecnrypted pages.
* We may start or fihish reading from/in the middle of the page (walreceiver)
* in such a case we should preserve the last read page header for
* the IV data and the encryption state.
* Read the buf page by page and decypt ecnrypted pages. We may start or
* fihish reading from/in the middle of the page (walreceiver) in such a
* case we should preserve the last read page header for the IV data and
* the encryption state.
*
* TODO: check if walsender/receiver restarts form the beggining of the page
* in case of the crash.
* TODO: check if walsender/receiver restarts form the beggining of the
* page in case of the crash.
*/
for (dec_off = 0; dec_off < readsz;)
{

@ -200,6 +200,7 @@ create_principal_key(const char *key_name, GenericKeyring * keyring,
}
principalKey->keyLength = keyInfo->data.len;
memcpy(principalKey->keyData, keyInfo->data.data, keyInfo->data.len);
return principalKey;

@ -120,7 +120,8 @@ tde_provider_info_lock(void)
return &sharedPrincipalKeyState->Locks[TDE_LWLOCK_PI_FILES].lock;
}
void InitializeKeyProviderInfo(void)
void
InitializeKeyProviderInfo(void)
{
ereport(LOG, (errmsg("initializing TDE key provider info")));
RegisterShmemRequest(&key_provider_info_shmem_routine);
@ -178,6 +179,7 @@ GetKeyProviderByName(const char *provider_name, Oid dbOid, Oid spcOid)
{
GenericKeyring *keyring = NULL;
List *providers = scan_key_provider_file(PROVIDER_SCAN_BY_NAME, (void *) provider_name, dbOid, spcOid);
if (providers != NIL)
{
keyring = (GenericKeyring *) linitial(providers);
@ -221,7 +223,10 @@ write_key_provider_info(KeyringProvideRecord *provider, Oid database_id,
}
if (position == -1)
{
/* we also need to verify the name conflict and generate the next provider ID */
/*
* we also need to verify the name conflict and generate the next
* provider ID
*/
while (fetch_next_key_provider(fd, &curr_pos, &existing_provider))
{
if (strcmp(existing_provider.provider_name, provider->provider_name) == 0)
@ -244,7 +249,8 @@ write_key_provider_info(KeyringProvideRecord *provider, Oid database_id,
provider->provider_id = max_provider_id + 1;
curr_pos = lseek(fd, 0, SEEK_END);
/* emit the xlog here. So that we can handle partial file write errors
/*
* emit the xlog here. So that we can handle partial file write errors
* but cannot make new WAL entries during recovery.
*/
if (write_xlog)
@ -263,12 +269,14 @@ write_key_provider_info(KeyringProvideRecord *provider, Oid database_id,
}
else
{
/* we are performing redo, just go to the position received from the
* xlog and write the record there.
* No need to verify the name conflict and generate the provider ID
/*
* we are performing redo, just go to the position received from the
* xlog and write the record there. No need to verify the name
* conflict and generate the provider ID
*/
curr_pos = lseek(fd, position, SEEK_SET);
}
/*
* All good, Just add a new provider
*/
@ -365,6 +373,7 @@ pg_tde_list_all_key_providers(PG_FUNCTION_ARGS)
MemoryContext per_query_ctx;
MemoryContext oldcontext;
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
ereport(ERROR,
@ -414,6 +423,7 @@ GetKeyProviderByID(int provider_id, Oid dbOid, Oid spcOid)
{
GenericKeyring *keyring = NULL;
List *providers = scan_key_provider_file(PROVIDER_SCAN_BY_ID, &provider_id, dbOid, spcOid);
if (providers != NIL)
{
keyring = (GenericKeyring *) linitial(providers);
@ -430,6 +440,7 @@ GetKeyProviderByID(int provider_id, Oid dbOid, Oid spcOid)
{
GenericKeyring *keyring = NULL;
SimplePtrList *providers = scan_key_provider_file(PROVIDER_SCAN_BY_ID, &provider_id, dbOid, spcOid);
if (providers != NULL)
{
keyring = (GenericKeyring *) providers->head->ptr;
@ -494,6 +505,7 @@ scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid, Oid
while (fetch_next_key_provider(fd, &curr_pos, &provider))
{
bool match = false;
ereport(DEBUG2,
(errmsg("read key provider ID=%d %s", provider.provider_id, provider.provider_name)));
@ -518,6 +530,7 @@ scan_key_provider_file(ProviderScanType scanType, void *scanKey, Oid dbOid, Oid
if (match)
{
GenericKeyring *keyring = load_keyring_provider_from_record(&provider);
if (keyring)
{
#ifndef FRONTEND
@ -627,6 +640,7 @@ static void
debug_print_kerying(GenericKeyring *keyring)
{
int debug_level = DEBUG2;
elog(debug_level, "Keyring type: %d", keyring->type);
elog(debug_level, "Keyring name: %s", keyring->provider_name);
elog(debug_level, "Keyring id: %d", keyring->key_id);
@ -651,6 +665,7 @@ static char*
get_keyring_infofile_path(char *resPath, Oid dbOid, Oid spcOid)
{
char *db_path = pg_tde_get_tde_file_dir(dbOid, spcOid);
Assert(db_path != NULL);
join_path_components(resPath, db_path, PG_TDE_KEYRING_FILENAME);
pfree(db_path);

@ -53,8 +53,7 @@ typedef struct TdePrincipalKeylocalState
{
TdePrincipalKeySharedState *sharedPrincipalKeyState;
dsa_area *dsa; /* local dsa area for backend attached to the
* dsa area created by postmaster at startup.
*/
* dsa area created by postmaster at startup. */
dshash_table *sharedHash;
} TdePrincipalKeylocalState;
@ -95,7 +94,8 @@ static const TDEShmemSetupRoutine principal_key_info_shmem_routine = {
.shmem_kill = shared_memory_shutdown
};
void InitializePrincipalKeyInfo(void)
void
InitializePrincipalKeyInfo(void)
{
ereport(LOG, (errmsg("Initializing TDE principal key info")));
RegisterShmemRequest(&principal_key_info_shmem_routine);
@ -124,6 +124,7 @@ static Size
required_shared_mem_size(void)
{
Size sz = cache_area_size();
sz = add_size(sz, sizeof(TdePrincipalKeySharedState));
return MAXALIGN(sz);
}
@ -137,6 +138,7 @@ static Size
initialize_shared_state(void *start_address)
{
TdePrincipalKeySharedState *sharedState = (TdePrincipalKeySharedState *) start_address;
ereport(LOG, (errmsg("initializing shared state for principal key")));
principalKeyLocalState.dsa = NULL;
principalKeyLocalState.sharedHash = NULL;
@ -147,7 +149,8 @@ initialize_shared_state(void *start_address)
return sizeof(TdePrincipalKeySharedState);
}
void initialize_objects_in_dsa_area(dsa_area *dsa, void *raw_dsa_area)
void
initialize_objects_in_dsa_area(dsa_area *dsa, void *raw_dsa_area)
{
dshash_table *dsh;
TdePrincipalKeySharedState *sharedState = principalKeyLocalState.sharedPrincipalKeyState;
@ -263,6 +266,7 @@ set_principal_key_with_keyring(const char *key_name, GenericKeyring *keyring,
}
principalKey->keyLength = keyInfo->data.len;
memcpy(principalKey->keyData, keyInfo->data.data, keyInfo->data.len);
save_principal_key_info(&principalKey->keyInfo);
@ -280,8 +284,8 @@ set_principal_key_with_keyring(const char *key_name, GenericKeyring *keyring,
if (is_dup_key)
{
/*
* Seems like just before we got the lock, the key was installed by some other caller
* Throw an error and mover no
* Seems like just before we got the lock, the key was installed by
* some other caller Throw an error and mover no
*/
ereport(ERROR,
@ -322,9 +326,9 @@ RotatePrincipalKey(TDEPrincipalKey *current_key, const char *new_key_name, const
oldCtx = MemoryContextSwitchTo(keyRotateCtx);
/*
* Let's set everything the same as the older principal key and
* update only the required attributes.
* */
* Let's set everything the same as the older principal key and update
* only the required attributes.
*/
memcpy(&new_principal_key, current_key, sizeof(TDEPrincipalKey));
if (new_key_name == NULL)
@ -361,9 +365,11 @@ RotatePrincipalKey(TDEPrincipalKey *current_key, const char *new_key_name, const
}
new_principal_key.keyLength = keyInfo->data.len;
memcpy(new_principal_key.keyData, keyInfo->data.data, keyInfo->data.len);
is_rotated = pg_tde_perform_rotate_key(current_key, &new_principal_key);
if (is_rotated && current_key->keyInfo.tablespaceId != GLOBALTABLESPACE_OID) {
if (is_rotated && current_key->keyInfo.tablespaceId != GLOBALTABLESPACE_OID)
{
clear_principal_key_cache(current_key->keyInfo.databaseId);
push_principal_key_to_cache(&new_principal_key);
}
@ -399,12 +405,15 @@ load_latest_versioned_key_name(TDEPrincipalKeyInfo *principal_key_info, GenericK
KeyringReturnCodes kr_ret;
keyInfo *keyInfo = NULL;
int base_version = principal_key_info->keyId.version;
Assert(principal_key_info != NULL);
Assert(keyring != NULL);
Assert(strlen(principal_key_info->keyId.name) > 0);
/* Start with the passed in version number
* We expect the name and the version number are already properly initialized
* and contain the correct values
/*
* Start with the passed in version number We expect the name and the
* version number are already properly initialized and contain the correct
* values
*/
snprintf(principal_key_info->keyId.versioned_name, TDE_KEY_NAME_LEN,
"%s_%d", principal_key_info->keyId.name, principal_key_info->keyId.version);
@ -412,7 +421,11 @@ load_latest_versioned_key_name(TDEPrincipalKeyInfo *principal_key_info, GenericK
while (true)
{
keyInfo = KeyringGetKey(keyring, principal_key_info->keyId.versioned_name, false, &kr_ret);
/* vault-v2 returns 404 (KEYRING_CODE_RESOURCE_NOT_AVAILABLE) when key is not found */
/*
* vault-v2 returns 404 (KEYRING_CODE_RESOURCE_NOT_AVAILABLE) when key
* is not found
*/
if (kr_ret != KEYRING_CODE_SUCCESS && kr_ret != KEYRING_CODE_RESOURCE_NOT_AVAILABLE)
{
ereport(FATAL,
@ -443,7 +456,8 @@ load_latest_versioned_key_name(TDEPrincipalKeyInfo *principal_key_info, GenericK
snprintf(principal_key_info->keyId.versioned_name, TDE_KEY_NAME_LEN, "%s_%d", principal_key_info->keyId.name, principal_key_info->keyId.version);
/*
* Not really required. Just to break the infinite loop in case the key provider is not behaving sane.
* Not really required. Just to break the infinite loop in case the
* key provider is not behaving sane.
*/
if (principal_key_info->keyId.version > MAX_PRINCIPAL_KEY_VERSION_NUM)
{
@ -453,6 +467,7 @@ load_latest_versioned_key_name(TDEPrincipalKeyInfo *principal_key_info, GenericK
}
return NULL; /* Just to keep compiler quite */
}
/*
* Returns the provider ID of the keyring that holds the principal key
* Return InvalidOid if the principal key is not set for the database
@ -474,7 +489,10 @@ GetPrincipalKeyProviderId(void)
keyringId = principalKey->keyInfo.keyringId;
}
{
/* Principal key not present in cache. Try Loading it from the info file */
/*
* Principal key not present in cache. Try Loading it from the info
* file
*/
principalKeyInfo = pg_tde_get_principal_key_info(dbOid, MyDatabaseTableSpace);
if (principalKeyInfo)
{
@ -532,6 +550,7 @@ push_principal_key_to_cache(TDEPrincipalKey *principalKey)
TDEPrincipalKey *cacheEntry = NULL;
Oid databaseId = principalKey->keyInfo.databaseId;
bool found = false;
cacheEntry = dshash_find_or_insert(get_principal_key_Hash(),
&databaseId, &found);
if (!found)
@ -570,6 +589,7 @@ void
cleanup_principal_key_info(Oid databaseId, Oid tablespaceId)
{
clear_principal_key_cache(databaseId);
/*
* TODO: Although should never happen. Still verify if any table in the
* database is using tde
@ -599,7 +619,8 @@ clear_principal_key_cache(Oid databaseId)
PG_FUNCTION_INFO_V1(pg_tde_set_principal_key);
Datum pg_tde_set_principal_key(PG_FUNCTION_ARGS);
Datum pg_tde_set_principal_key(PG_FUNCTION_ARGS)
Datum
pg_tde_set_principal_key(PG_FUNCTION_ARGS)
{
char *principal_key_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
char *provider_name = text_to_cstring(PG_GETARG_TEXT_PP(1));
@ -656,7 +677,8 @@ pg_tde_rotate_principal_key_internal(PG_FUNCTION_ARGS)
}
PG_FUNCTION_INFO_V1(pg_tde_principal_key_info_internal);
Datum pg_tde_principal_key_info_internal(PG_FUNCTION_ARGS)
Datum
pg_tde_principal_key_info_internal(PG_FUNCTION_ARGS)
{
Oid dbOid = MyDatabaseId;
Oid spcOid = MyDatabaseTableSpace;
@ -770,6 +792,7 @@ get_principal_key_from_keyring(Oid dbOid, Oid spcOid)
}
keyInfo = KeyringGetKey(keyring, principalKeyInfo->keyId.versioned_name, false, &keyring_ret);
if (keyInfo == NULL)
{
return NULL;

@ -27,8 +27,7 @@ typedef struct TDELocalState
{
TdeSharedState *sharedTdeState;
dsa_area **dsa; /* local dsa area for backend attached to the
* dsa area created by postmaster at startup.
*/
* dsa area created by postmaster at startup. */
dshash_table *principalKeySharedHash;
} TDELocalState;
@ -37,19 +36,23 @@ static void tde_shmem_shutdown(int code, Datum arg);
List *registeredShmemRequests = NIL;
bool shmemInited = false;
void RegisterShmemRequest(const TDEShmemSetupRoutine *routine)
void
RegisterShmemRequest(const TDEShmemSetupRoutine *routine)
{
Assert(shmemInited == false);
registeredShmemRequests = lappend(registeredShmemRequests, (void *) routine);
}
Size TdeRequiredSharedMemorySize(void)
Size
TdeRequiredSharedMemorySize(void)
{
Size sz = 0;
ListCell *lc;
foreach(lc, registeredShmemRequests)
{
TDEShmemSetupRoutine *routine = (TDEShmemSetupRoutine *) lfirst(lc);
if (routine->required_shared_mem_size)
sz = add_size(sz, routine->required_shared_mem_size());
}
@ -57,12 +60,14 @@ Size TdeRequiredSharedMemorySize(void)
return MAXALIGN(sz);
}
int TdeRequiredLocksCount(void)
int
TdeRequiredLocksCount(void)
{
return TDE_LWLOCK_COUNT;
}
void TdeShmemInit(void)
void
TdeShmemInit(void)
{
bool found;
TdeSharedState *tdeState;
@ -89,6 +94,7 @@ void TdeShmemInit(void)
{
Size sz = 0;
TDEShmemSetupRoutine *routine = (TDEShmemSetupRoutine *) lfirst(lc);
if (routine->init_shared_state)
{
sz = routine->init_shared_state(p);
@ -113,12 +119,14 @@ void TdeShmemInit(void)
foreach(lc, registeredShmemRequests)
{
TDEShmemSetupRoutine *routine = (TDEShmemSetupRoutine *) lfirst(lc);
if (routine->init_dsa_area_objects)
routine->init_dsa_area_objects(dsa, tdeState->rawDsaArea);
}
ereport(LOG, (errmsg("setting no limit to DSA area of size %lu", dsa_area_size)));
dsa_set_size_limit(dsa, -1); /* Let it grow outside the shared memory */
dsa_set_size_limit(dsa, -1); /* Let it grow outside the shared
* memory */
shmemInited = true;
}
@ -130,9 +138,11 @@ static void
tde_shmem_shutdown(int code, Datum arg)
{
ListCell *lc;
foreach(lc, registeredShmemRequests)
{
TDEShmemSetupRoutine *routine = (TDEShmemSetupRoutine *) lfirst(lc);
if (routine->shmem_kill)
routine->shmem_kill(code, arg);
}

@ -78,6 +78,7 @@ get_tde_tables_count(void)
{
List *tde_tables = get_all_tde_tables();
int count = list_length(tde_tables);
list_free(tde_tables);
return count;
}
@ -97,8 +98,10 @@ pg_tde_set_globalspace_dir(const char *dir)
char *
pg_tde_get_tde_file_dir(Oid dbOid, Oid spcOid)
{
/* `dbOid` is set to a value for the XLog keys caching but GetDatabasePath()
* expects it (`dbOid`) to be `0` if this is a global space.
/*
* `dbOid` is set to a value for the XLog keys caching but
* GetDatabasePath() expects it (`dbOid`) to be `0` if this is a global
* space.
*/
if (spcOid == GLOBALTABLESPACE_OID)
{

@ -48,23 +48,26 @@ const EVP_CIPHER* cipher = NULL;
const EVP_CIPHER *cipher2 = NULL;
int cipher_block_size = 0;
void AesInit(void)
void
AesInit(void)
{
static int initialized = 0;
if(!initialized) {
if (!initialized)
{
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
cipher = EVP_aes_128_cbc();
cipher_block_size = EVP_CIPHER_block_size(cipher); // == buffer size
cipher_block_size = EVP_CIPHER_block_size(cipher);
//== buffer size
cipher2 = EVP_aes_128_ecb();
initialized = 1;
}
}
// TODO: a few things could be optimized in this. It's good enough for a prototype.
/* TODO: a few things could be optimized in this. It's good enough for a prototype. */
static void
AesRunCtr(EVP_CIPHER_CTX **ctxPtr, int enc, const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out, int *out_len)
{
@ -100,10 +103,12 @@ AesRunCtr(EVP_CIPHER_CTX** ctxPtr, int enc, const unsigned char* key, const unsi
}
}
static void AesRunCbc(int enc, const unsigned char* key, const unsigned char* iv, const unsigned char* in, int in_len, unsigned char* out, int* out_len)
static void
AesRunCbc(int enc, const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out, int *out_len)
{
int out_len_final = 0;
EVP_CIPHER_CTX *ctx = NULL;
ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(ctx);
@ -143,8 +148,9 @@ static void AesRunCbc(int enc, const unsigned char* key, const unsigned char* iv
goto cleanup;
}
/* We encrypt one block (16 bytes)
* Our expectation is that the result should also be 16 bytes, without any additional padding
/*
* We encrypt one block (16 bytes) Our expectation is that the result
* should also be 16 bytes, without any additional padding
*/
*out_len += out_len_final;
Assert(in_len == *out_len);
@ -154,19 +160,22 @@ cleanup:
EVP_CIPHER_CTX_free(ctx);
}
void AesEncrypt(const unsigned char* key, const unsigned char* iv, const unsigned char* in, int in_len, unsigned char* out, int* out_len)
void
AesEncrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out, int *out_len)
{
AesRunCbc(1, key, iv, in, in_len, out, out_len);
}
void AesDecrypt(const unsigned char* key, const unsigned char* iv, const unsigned char* in, int in_len, unsigned char* out, int* out_len)
void
AesDecrypt(const unsigned char *key, const unsigned char *iv, const unsigned char *in, int in_len, unsigned char *out, int *out_len)
{
AesRunCbc(0, key, iv, in, in_len, out, out_len);
}
/* This function assumes that the out buffer is big enough: at least (blockNumber2 - blockNumber1) * 16 bytes
*/
void Aes128EncryptedZeroBlocks(void* ctxPtr, const unsigned char* key, const char* iv_prefix, uint64_t blockNumber1, uint64_t blockNumber2, unsigned char* out)
void
Aes128EncryptedZeroBlocks(void *ctxPtr, const unsigned char *key, const char *iv_prefix, uint64_t blockNumber1, uint64_t blockNumber2, unsigned char *out)
{
const unsigned char iv[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
@ -178,10 +187,10 @@ void Aes128EncryptedZeroBlocks(void* ctxPtr, const unsigned char* key, const cha
for (int j = blockNumber1; j < blockNumber2; ++j)
{
/*
* We have 16 bytes, and a 4 byte counter. The counter is the last 4 bytes.
* Technically, this isn't correct: the byte order of the counter depends
* on the endianness of the CPU running it.
* As this is a generic limitation of Postgres, it's fine.
* We have 16 bytes, and a 4 byte counter. The counter is the last 4
* bytes. Technically, this isn't correct: the byte order of the
* counter depends on the endianness of the CPU running it. As this is
* a generic limitation of Postgres, it's fine.
*/
memcpy(out + (16 * (j - blockNumber1)), iv_prefix, 12);
memcpy(out + (16 * (j - blockNumber1)) + 12, (char *) &j, 4);

@ -11,9 +11,11 @@
#include "keyring/keyring_api.h"
#ifdef ENCRYPTION_DEBUG
static void iv_prefix_debug(const char* iv_prefix, char* out_hex)
static void
iv_prefix_debug(const char *iv_prefix, char *out_hex)
{
for (int i = 0; i < 16; ++i)
{
for(int i =0;i<16;++i) {
sprintf(out_hex + i * 2, "%02x", (int) *(iv_prefix + i));
}
out_hex[32] = 0;
@ -23,11 +25,12 @@ static void iv_prefix_debug(const char* iv_prefix, char* out_hex)
static void
SetIVPrefix(ItemPointerData *ip, char *iv_prefix)
{
/* We have up to 16 bytes for the entire IV
* The higher bytes (starting with 15) are used for the incrementing counter
* The lower bytes (in this case, 0..5) are used for the tuple identification
* Tuple identification is based on CTID, which currently is 48 bytes in
* postgres: 4 bytes for the block id and 2 bytes for the position id
/*
* We have up to 16 bytes for the entire IV The higher bytes (starting
* with 15) are used for the incrementing counter The lower bytes (in this
* case, 0..5) are used for the tuple identification Tuple identification
* is based on CTID, which currently is 48 bytes in postgres: 4 bytes for
* the block id and 2 bytes for the position id
*/
iv_prefix[0] = ip->ip_blkid.bi_hi / 256;
iv_prefix[1] = ip->ip_blkid.bi_hi % 256;
@ -65,6 +68,7 @@ pg_tde_crypt_simple(const char* iv_prefix, uint32 start_offset, const char* data
#ifdef ENCRYPTION_DEBUG
{
char ivp_debug[33];
iv_prefix_debug(iv_prefix, ivp_debug);
ereport(LOG,
(errmsg("%s: Start offset: %lu Data_Len: %u, aes_start_block: %lu, aes_end_block: %lu, IV prefix: %s",
@ -106,6 +110,7 @@ pg_tde_crypt_complex(const char* iv_prefix, uint32 start_offset, const char* dat
#ifdef ENCRYPTION_DEBUG
{
char ivp_debug[33];
iv_prefix_debug(iv_prefix, ivp_debug);
ereport(LOG,
(errmsg("%s: Batch-No:%d Start offset: %lu Data_Len: %u, batch_start_block: %lu, batch_end_block: %lu, IV prefix: %s",
@ -114,29 +119,31 @@ pg_tde_crypt_complex(const char* iv_prefix, uint32 start_offset, const char* dat
#endif
current_batch_bytes = ((batch_end_block - batch_start_block) * AES_BLOCK_SIZE)
- (batch_no > 0 ? 0 : aes_block_no); /* first batch skips `aes_block_no`-th bytes of enc_key */
- (batch_no > 0 ? 0 : aes_block_no); /* first batch skips
* `aes_block_no`-th bytes
* of enc_key */
if ((data_index + current_batch_bytes) > data_len)
current_batch_bytes = data_len - data_index;
for (uint32 i = 0; i < current_batch_bytes; ++i)
{
/*
* As the size of enc_key always is a multiple of 16 we
* start from `aes_block_no`-th index of the enc_key[]
* so N-th will be crypted with the same enc_key byte despite
* what start_offset the function was called with.
* For example start_offset = 10; MAX_AES_ENC_BATCH_KEY_SIZE = 6:
* data: [10 11 12 13 14 15 16]
* encKey: [...][0 1 2 3 4 5][0 1 2 3 4 5]
* so the 10th data byte is encoded with the 4th byte of the 2nd enc_key etc.
* We need this shift so each byte will be coded the same despite
* the initial offset.
* Let's see the same data but sent to the func starting from the offset 0:
* data: [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16]
* encKey: [0 1 2 3 4 5][0 1 2 3 4 5][ 0 1 2 3 4 5]
* again, the 10th data byte is encoded with the 4th byte of the 2nd enc_key etc.
* As the size of enc_key always is a multiple of 16 we start from
* `aes_block_no`-th index of the enc_key[] so N-th will be
* crypted with the same enc_key byte despite what start_offset
* the function was called with. For example start_offset = 10;
* MAX_AES_ENC_BATCH_KEY_SIZE = 6: data: [10 11 12
* 13 14 15 16] encKey: [...][0 1 2 3 4 5][0 1 2 3 4 5] so
* the 10th data byte is encoded with the 4th byte of the 2nd
* enc_key etc. We need this shift so each byte will be coded the
* same despite the initial offset. Let's see the same data but
* sent to the func starting from the offset 0: data: [0 1 2 3
* 4 5 6 7 8 9 10 11 12 13 14 15 16] encKey: [0 1 2 3 4 5][0 1 2 3
* 4 5][ 0 1 2 3 4 5] again, the 10th data byte is encoded
* with the 4th byte of the 2nd enc_key etc.
*/
uint32 enc_key_index = i + (batch_no > 0 ? 0 : aes_block_no);
out[data_index] = data[data_index] ^ enc_key[enc_key_index];
data_index++;
@ -157,7 +164,8 @@ pg_tde_crypt(const char* iv_prefix, uint32 start_offset, const char* data, uint3
if (data_len >= DATA_BYTES_PER_AES_BATCH)
{
pg_tde_crypt_complex(iv_prefix, start_offset, data, data_len, out, key, context);
} else
}
else
{
pg_tde_crypt_simple(iv_prefix, start_offset, data, data_len, out, key, context);
}
@ -191,9 +199,9 @@ pg_tde_crypt_tuple(HeapTuple tuple, HeapTuple out_tuple, RelKeyData* key, const
}
// ================================================================
// HELPER FUNCTIONS FOR ENCRYPTION
// ================================================================
/* ================================================================ */
/* HELPER FUNCTIONS FOR ENCRYPTION */
/* ================================================================ */
OffsetNumber
PGTdePageAddItemExtended(RelFileLocator rel,
@ -212,6 +220,7 @@ PGTdePageAddItemExtended(RelFileLocator rel,
char *toAddr = ((char *) phdr) + phdr->pd_upper + header_size;
char *data = item + header_size;
uint32 data_len = size - header_size;
/* ctid stored in item is incorrect (not set) at this point */
ItemPointerData ip;
RelKeyData *key = GetHeapBaiscRelationKey(rel);
@ -257,7 +266,8 @@ AesEncryptKey(const TDEPrincipalKey *principal_key, const RelFileLocator *rlocat
* to note that memory is allocated in the TopMemoryContext so we expect this to be added
* to our key cache.
*/
void AesDecryptKey(const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, RelKeyData **p_rel_key_data, RelKeyData *enc_rel_key_data, size_t *key_bytes)
void
AesDecryptKey(const TDEPrincipalKey *principal_key, const RelFileLocator *rlocator, RelKeyData **p_rel_key_data, RelKeyData *enc_rel_key_data, size_t *key_bytes)
{
unsigned char iv[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

@ -22,7 +22,8 @@
#define FILE_KEYRING_TYPE "file"
#define VAULTV2_KEYRING_TYPE "vault-v2"
#define MAX_PROVIDER_NAME_LEN 128 /* pg_tde_key_provider's provider_name size*/
#define MAX_PROVIDER_NAME_LEN 128 /* pg_tde_key_provider's provider_name
* size */
#define MAX_VAULT_V2_KEY_LEN 128 /* From hashi corp docs */
#define MAX_KEYRING_OPTION_LEN 1024
typedef enum ProviderType

@ -27,26 +27,29 @@ typedef enum
typedef struct TDEShmemSetupRoutine
{
/* init_shared_state gets called at the time of extension load
* you can initialize the data structures required to be placed in
* shared memory in this callback
* The callback must return the size of the shared memory area acquired.
* The argument to the function is the start of the shared memory address
* that can be used to store the shared data structures.
/*
* init_shared_state gets called at the time of extension load you can
* initialize the data structures required to be placed in shared memory
* in this callback The callback must return the size of the shared memory
* area acquired. The argument to the function is the start of the shared
* memory address that can be used to store the shared data structures.
*/
Size (*init_shared_state) (void *raw_dsa_area);
/*
* shmem_startup gets called at the time of postmaster shutdown
*/
void (*shmem_kill) (int code, Datum arg);
/*
* The callback must return the size of the shared memory acquired.
*/
Size (*required_shared_mem_size) (void);
/*
* Gets called after all shared memory structures are initialized and
* here you can create shared memory hash tables or any other shared
* objects that needs to live in DSA area.
* Gets called after all shared memory structures are initialized and here
* you can create shared memory hash tables or any other shared objects
* that needs to live in DSA area.
*/
void (*init_dsa_area_objects) (dsa_area *dsa, void *raw_dsa_area);
} TDEShmemSetupRoutine;

@ -19,7 +19,8 @@
#include <stdbool.h>
#include <curl/curl.h>
typedef struct CurlString {
typedef struct CurlString
{
char *ptr;
size_t len;
} CurlString;
@ -28,4 +29,4 @@ extern CURL* keyringCurl;
bool curlSetupSession(const char *url, const char *caFile, CurlString *outStr);
#endif //KEYRING_CURL_H
#endif /* //KEYRING_CURL_H */

@ -14,4 +14,4 @@
extern bool InstallVaultV2Keyring(void);
#endif // KEYRING_FILE_H
#endif /* KEYRING_FILE_H */

@ -19,10 +19,10 @@
* ----------
*/
//#define ENCRYPTION_DEBUG 1
//#define KEYRING_DEBUG 1
//#define TDE_FORK_DEBUG 1
// #define TDE_XLOG_DEBUG 1
/* #define ENCRYPTION_DEBUG 1 */
/* #define KEYRING_DEBUG 1 */
/* #define TDE_FORK_DEBUG 1 */
/* #define TDE_XLOG_DEBUG 1 */
#define tdeheap_fill_tuple heap_fill_tuple
#define tdeheap_form_tuple heap_form_tuple

@ -34,9 +34,11 @@ static KeyProviders *
find_key_provider(ProviderType type)
{
ListCell *lc;
foreach(lc, registeredKeyProviders)
{
KeyProviders *kp = (KeyProviders *) lfirst(lc);
if (kp->type == type)
{
return kp;
@ -49,9 +51,11 @@ static KeyProviders *
find_key_provider(ProviderType type)
{
SimplePtrListCell *lc;
for (lc = registeredKeyProviders.head; lc; lc = lc->next)
{
KeyProviders *kp = (KeyProviders *) lc->ptr;
if (kp->type == type)
{
return kp;
@ -61,7 +65,8 @@ find_key_provider(ProviderType type)
}
#endif /* !FRONTEND */
bool RegisterKeyProvider(const TDEKeyringRoutine *routine, ProviderType type)
bool
RegisterKeyProvider(const TDEKeyringRoutine *routine, ProviderType type)
{
KeyProviders *kp;
@ -79,6 +84,7 @@ bool RegisterKeyProvider(const TDEKeyringRoutine *routine, ProviderType type)
#ifndef FRONTEND
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(TopMemoryContext);
#endif
kp = palloc(sizeof(KeyProviders));
@ -98,6 +104,7 @@ keyInfo *
KeyringGetKey(GenericKeyring *keyring, const char *key_name, bool throw_error, KeyringReturnCodes * returnCode)
{
KeyProviders *kp = find_key_provider(keyring->type);
if (kp == NULL)
{
ereport(throw_error ? ERROR : WARNING,
@ -112,6 +119,7 @@ KeyringReturnCodes
KeyringStoreKey(GenericKeyring *keyring, keyInfo *key, bool throw_error)
{
KeyProviders *kp = find_key_provider(keyring->type);
if (kp == NULL)
{
ereport(throw_error ? ERROR : WARNING,
@ -125,6 +133,7 @@ keyInfo *
KeyringGenerateNewKey(const char *key_name, unsigned key_len)
{
keyInfo *key;
Assert(key_len <= 32);
key = palloc(sizeof(keyInfo));
key->data.len = key_len;
@ -141,6 +150,7 @@ keyInfo *
KeyringGenerateNewKeyAndStore(GenericKeyring *keyring, const char *key_name, unsigned key_len, bool throw_error)
{
keyInfo *key = KeyringGenerateNewKey(key_name, key_len);
if (key == NULL)
{
ereport(throw_error ? ERROR : WARNING,

@ -17,11 +17,14 @@
CURL *keyringCurl = NULL;
static
size_t write_func(void *ptr, size_t size, size_t nmemb, struct CurlString *s)
size_t
write_func(void *ptr, size_t size, size_t nmemb, struct CurlString *s)
{
size_t new_len = s->len + size * nmemb;
s->ptr = repalloc(s->ptr, new_len + 1);
if (s->ptr == NULL) {
if (s->ptr == NULL)
{
exit(EXIT_FAILURE);
}
memcpy(s->ptr + s->len, ptr, size * nmemb);
@ -31,33 +34,49 @@ size_t write_func(void *ptr, size_t size, size_t nmemb, struct CurlString *s)
return size * nmemb;
}
bool curlSetupSession(const char* url, const char* caFile, CurlString* outStr)
bool
curlSetupSession(const char *url, const char *caFile, CurlString *outStr)
{
if (keyringCurl == NULL)
{
keyringCurl = curl_easy_init();
if(keyringCurl == NULL) return 0;
} else {
if (keyringCurl == NULL)
return 0;
}
else
{
curl_easy_reset(keyringCurl);
}
if(curl_easy_setopt(keyringCurl, CURLOPT_SSL_VERIFYPEER, 1) != CURLE_OK) return 0;
if(curl_easy_setopt(keyringCurl, CURLOPT_USE_SSL, CURLUSESSL_ALL) != CURLE_OK) return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_SSL_VERIFYPEER, 1) != CURLE_OK)
return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_USE_SSL, CURLUSESSL_ALL) != CURLE_OK)
return 0;
if (caFile != NULL && strlen(caFile) != 0)
{
if(curl_easy_setopt(keyringCurl, CURLOPT_CAINFO, caFile) != CURLE_OK) return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_CAINFO, caFile) != CURLE_OK)
return 0;
}
if(curl_easy_setopt(keyringCurl, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK) return 0;
if(curl_easy_setopt(keyringCurl, CURLOPT_CONNECTTIMEOUT, 3) != CURLE_OK) return 0;
if(curl_easy_setopt(keyringCurl, CURLOPT_TIMEOUT, 10) != CURLE_OK) return 0;
if(curl_easy_setopt(keyringCurl, CURLOPT_HTTP_VERSION,(long)CURL_HTTP_VERSION_1_1) != CURLE_OK) return 0;
if(curl_easy_setopt(keyringCurl, CURLOPT_WRITEFUNCTION,write_func) != CURLE_OK) return 0;
if(curl_easy_setopt(keyringCurl, CURLOPT_WRITEDATA,outStr) != CURLE_OK) return 0;
if(curl_easy_setopt(keyringCurl, CURLOPT_URL, url) != CURLE_OK) return 0;
if(curl_easy_setopt(keyringCurl, CURLOPT_POSTFIELDS, NULL) != CURLE_OK) return 0;
if(curl_easy_setopt(keyringCurl, CURLOPT_POST, 0) != CURLE_OK) return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK)
return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_CONNECTTIMEOUT, 3) != CURLE_OK)
return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_TIMEOUT, 10) != CURLE_OK)
return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_HTTP_VERSION, (long) CURL_HTTP_VERSION_1_1) != CURLE_OK)
return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_WRITEFUNCTION, write_func) != CURLE_OK)
return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_WRITEDATA, outStr) != CURLE_OK)
return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_URL, url) != CURLE_OK)
return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_POSTFIELDS, NULL) != CURLE_OK)
return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_POST, 0) != CURLE_OK)
return 0;
return 1;
}

@ -65,7 +65,8 @@ get_key_by_name(GenericKeyring* keyring, const char* key_name, bool throw_error,
if (bytes_read == 0)
{
/*
* Empty keyring file is considered as a valid keyring file that has no keys
* Empty keyring file is considered as a valid keyring file that
* has no keys
*/
close(fd);
pfree(key);

@ -77,7 +77,8 @@ const TDEKeyringRoutine keyringVaultV2Routine = {
};
bool InstallVaultV2Keyring(void)
bool
InstallVaultV2Keyring(void)
{
return RegisterKeyProvider(&keyringVaultV2Routine, VAULT_V2_KEY_PROVIDER);
}
@ -88,17 +89,21 @@ curl_setup_token(VaultV2Keyring *keyring)
if (curlList == NULL)
{
char tokenHeader[256];
strcpy(tokenHeader, "X-Vault-Token:");
strcat(tokenHeader, keyring->vault_token);
curlList = curl_slist_append(curlList, tokenHeader);
if(curlList == NULL) return 0;
if (curlList == NULL)
return 0;
curlList = curl_slist_append(curlList, "Content-Type: application/json");
if(curlList == NULL) return 0;
if (curlList == NULL)
return 0;
}
if(curl_easy_setopt(keyringCurl, CURLOPT_HTTPHEADER, curlList) != CURLE_OK) return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_HTTPHEADER, curlList) != CURLE_OK)
return 0;
return 1;
}
@ -125,7 +130,8 @@ curl_perform(VaultV2Keyring *keyring, const char *url, CurlString *outStr, long
if (postData != NULL)
{
if(curl_easy_setopt(keyringCurl, CURLOPT_POSTFIELDS, postData) != CURLE_OK) return 0;
if (curl_easy_setopt(keyringCurl, CURLOPT_POSTFIELDS, postData) != CURLE_OK)
return 0;
}
ret = curl_easy_perform(keyringCurl);
@ -135,7 +141,8 @@ curl_perform(VaultV2Keyring *keyring, const char *url, CurlString *outStr, long
return 0;
}
if(curl_easy_getinfo(keyringCurl, CURLINFO_RESPONSE_CODE, httpCode) != CURLE_OK) return 0;
if (curl_easy_getinfo(keyringCurl, CURLINFO_RESPONSE_CODE, httpCode) != CURLE_OK)
return 0;
#if KEYRING_DEBUG
elog(DEBUG2, "Vault response [%li] '%s'", *httpCode, outStr->ptr != NULL ? outStr->ptr : "");
@ -172,8 +179,11 @@ set_key_by_name(GenericKeyring* keyring, keyInfo *key, bool throw_error)
Assert(key != NULL);
// Since we are only building a very limited JSON with a single base64 string, we build it by hand
// Simpler than using the limited pg json api
/*
* Since we are only building a very limited JSON with a single base64
* string, we build it by hand
*/
/* Simpler than using the limited pg json api */
keyLen = pg_b64_encode((char *) key->data.data, key->data.len, keyData, 64);
keyData[keyLen] = 0;

@ -122,14 +122,20 @@ _PG_init(void)
RegisterStorageMgr();
}
Datum pg_tde_extension_initialize(PG_FUNCTION_ARGS)
Datum
pg_tde_extension_initialize(PG_FUNCTION_ARGS)
{
/* Initialize the TDE map */
XLogExtensionInstall xlrec;
xlrec.database_id = MyDatabaseId;
xlrec.tablespace_id = MyDatabaseTableSpace;
run_extension_install_callbacks(&xlrec, false);
/* Also put this info in xlog, so we can replicate the same on the other side */
/*
* Also put this info in xlog, so we can replicate the same on the other
* side
*/
XLogBeginInsert();
XLogRegisterData((char *) &xlrec, sizeof(XLogExtensionInstall));
XLogInsert(RM_TDERMGR_ID, XLOG_TDE_EXTENSION_INSTALL_KEY);
@ -149,7 +155,8 @@ extension_install_redo(XLogExtensionInstall *xlrec)
* run at the time of pg_tde extension installs.
* ----------------------------------------------------------------
*/
void on_ext_install(pg_tde_on_ext_install_callback function, void *arg)
void
on_ext_install(pg_tde_on_ext_install_callback function, void *arg)
{
if (on_ext_install_index >= MAX_ON_INSTALLS)
ereport(FATAL,
@ -171,10 +178,10 @@ run_extension_install_callbacks(XLogExtensionInstall* xlrec , bool redo)
{
int i;
int tde_table_count = 0;
/*
* Get the number of tde tables in this database
* should always be zero. But still, it prevents
* the cleanup if someone explicitly calls this
* Get the number of tde tables in this database should always be zero.
* But still, it prevents the cleanup if someone explicitly calls this
* function.
*/
if (!redo)

@ -110,7 +110,8 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS)
if (stmt->accessMethod && strcmp(stmt->accessMethod, "tde_heap") == 0)
{
tdeCurrentCreateEvent.encryptMode = true;
} else if ((stmt->accessMethod == NULL || stmt->accessMethod[0] ==0) && strcmp(default_table_access_method, "tde_heap") == 0)
}
else if ((stmt->accessMethod == NULL || stmt->accessMethod[0] == 0) && strcmp(default_table_access_method, "tde_heap") == 0)
{
tdeCurrentCreateEvent.encryptMode = true;
}
@ -151,7 +152,10 @@ pg_tde_ddl_command_start_capture(PG_FUNCTION_ARGS)
}
}
// TODO: also check for tablespace change, if current or new AM is tde_heap!
/*
* TODO: also check for tablespace change, if current or new AM is
* tde_heap!
*/
if (tdeCurrentCreateEvent.encryptMode)
{

@ -14,6 +14,7 @@ typedef struct TDESMgrRelationData
{
/* parent data */
SMgrRelationData reln;
/*
* for md.c; per-fork arrays of the number of open segments
* (md_num_open_segs) and the segments themselves (md_seg_fds).
@ -45,7 +46,7 @@ tde_smgr_get_key(SMgrRelation reln)
if (IsCatalogRelationOid(reln->smgr_rlocator.locator.relNumber))
{
// do not try to encrypt/decrypt catalog tables
/* do not try to encrypt/decrypt catalog tables */
return NULL;
}
@ -59,7 +60,7 @@ tde_smgr_get_key(SMgrRelation reln)
event = GetCurrentTdeCreateEvent();
// see if we have a key for the relation, and return if yes
/* see if we have a key for the relation, and return if yes */
rkd = GetSMGRRelationKey(reln->smgr_rlocator.locator);
if (rkd != NULL)
@ -67,17 +68,20 @@ tde_smgr_get_key(SMgrRelation reln)
return rkd;
}
// if this is a CREATE TABLE, we have to generate the key
/* if this is a CREATE TABLE, we have to generate the key */
if (event->encryptMode == true && event->eventType == TDE_TABLE_CREATE_EVENT)
{
return pg_tde_create_smgr_key(&reln->smgr_rlocator.locator);
}
// if this is a CREATE INDEX, we have to load the key based on the table
/* if this is a CREATE INDEX, we have to load the key based on the table */
if (event->encryptMode == true && event->eventType == TDE_INDEX_CREATE_EVENT)
{
// For now keep it simple and create separate key for indexes
// Later we might modify the map infrastructure to support the same keys
/* For now keep it simple and create separate key for indexes */
/*
* Later we might modify the map infrastructure to support the same
* keys
*/
return pg_tde_create_smgr_key(&reln->smgr_rlocator.locator);
}
@ -106,10 +110,12 @@ tde_mdwritev(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
for (int i = 0; i < nblocks; ++i)
{
int out_len = BLCKSZ;
local_buffers[i] = &local_blocks_aligned[i * BLCKSZ];
BlockNumber bn = blocknum + i;
unsigned char iv[16] = {0,};
memcpy(iv + 4, &bn, sizeof(BlockNumber));
AesEncrypt(rkd->internal_key.key, iv, ((char **) buffers)[i], BLCKSZ, local_buffers[i], &out_len);
@ -179,11 +185,20 @@ tde_mdreadv(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
{
if (((char **) buffers)[i][j] != 0)
{
// Postgres creates all zero blocks in an optimized route, which we do not try
// to encrypt.
// Instead we detect if a block is all zero at decryption time, and
// leave it as is.
// This could be a security issue later, but it is a good first prototype
/*
* Postgres creates all zero blocks in an optimized route,
* which we do not try
*/
/* to encrypt. */
/*
* Instead we detect if a block is all zero at decryption
* time, and
*/
/* leave it as is. */
/*
* This could be a security issue later, but it is a good
* first prototype
*/
allZero = false;
break;
}
@ -201,10 +216,19 @@ static void
tde_mdcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo)
{
TDESMgrRelation tdereln = (TDESMgrRelation) reln;
// This is the only function that gets called during actual CREATE TABLE/INDEX (EVENT TRIGGER)
// so we create the key here by loading it
// Later calls then decide to encrypt or not based on the existence of the key
/*
* This is the only function that gets called during actual CREATE
* TABLE/INDEX (EVENT TRIGGER)
*/
/* so we create the key here by loading it */
/*
* Later calls then decide to encrypt or not based on the existence of the
* key
*/
RelKeyData *key = tde_smgr_get_key(reln);
if (key)
{
tdereln->encrypted_relation = true;
@ -226,6 +250,7 @@ tde_mdopen(SMgrRelation reln)
{
TDESMgrRelation tdereln = (TDESMgrRelation) reln;
RelKeyData *key = tde_smgr_get_key(reln);
if (key)
{
tdereln->encrypted_relation = true;
@ -260,16 +285,18 @@ static const struct f_smgr tde_smgr = {
.smgr_registersync = mdregistersync,
};
void RegisterStorageMgr(void)
void
RegisterStorageMgr(void)
{
tde_smgr_id = smgr_register(&tde_smgr, sizeof(TDESMgrRelationData));
// TODO: figure out how this part should work in a real extension
/* TODO: figure out how this part should work in a real extension */
storage_manager_id = tde_smgr_id;
}
#else
void RegisterStorageMgr(void)
void
RegisterStorageMgr(void)
{
}
#endif /* PERCONA_EXT */

@ -65,7 +65,8 @@ pg_tde_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
ereport(DEBUG2,
(errmsg("pg_tde_subxact_callback: aborting subtransaction")));
do_pending_deletes(false);
} else if (event == SUBXACT_EVENT_COMMIT_SUB)
}
else if (event == SUBXACT_EVENT_COMMIT_SUB)
{
ereport(DEBUG2,
(errmsg("pg_tde_subxact_callback: committing subtransaction")));
@ -77,6 +78,7 @@ void
RegisterEntryForDeletion(const RelFileLocator *rlocator, off_t map_entry_offset, bool atCommit)
{
PendingMapEntryDelete *pending;
pending = (PendingMapEntryDelete *) MemoryContextAlloc(TopMemoryContext, sizeof(PendingMapEntryDelete));
pending->map_entry_offset = map_entry_offset;
memcpy(&pending->rlocator, rlocator, sizeof(RelFileLocator));

@ -0,0 +1,113 @@
BulkInsertStateData
BulkInsertStateData
BulkInsertStateData
BulkInsertStateData
CurlString
FileKeyring
GenericKeyring
HeapPageFreeze
HeapPageFreeze
HeapScanDescData
HeapScanDescData
HeapScanDescData
HeapScanDescData
HeapTupleFreeze
HeapTupleFreeze
IndexDeleteCounts
IndexDeleteCounts
IndexFetchHeapData
IndexFetchHeapData
InternalKey
JsonKeringSemState
JsonKeyringField
JsonKeyringState
JsonVaultRespField
JsonVaultRespSemState
JsonVaultRespState
KeyProviders
KeyringProvideRecord
KeyringProviderXLRecord
KeyringReturnCode
LVPagePruneState
LVRelState
LVRelState
LVSavedErrInfo
LVSavedErrInfo
LogicalRewriteMappingData
LogicalRewriteMappingData
PendingMapEntryDelete
ProviderScanType
PruneFreezeResult
RelKeyCache
RelKeyCacheRec
RelKeyData
RewriteMappingDataEntry
RewriteMappingDataEntry
RewriteMappingFile
RewriteMappingFile
RewriteStateData
RewriteStateData
RewriteStateData
RewriteStateData *RewriteState;
TDEBufferHeapTupleTableSlot
TDEFileHeader
TDEKeyringRoutine
TDELocalState
TDELockTypes
TDEMapEntry
TDEMapFilePath
TDEPrincipalKey
TDEPrincipalKeyId
TDEPrincipalKeyInfo
TDEShmemSetupRoutine
TdeCreateEvent
TdeCreateEventType
TdeKeyProviderInfoSharedState
TdePrincipalKeySharedState
TdePrincipalKeylocalState
TdeSharedState
VaultV2Keyring
XLogExtensionInstall
XLogPrincipalKeyRotate
XLogRelKey
itemIdCompactData
itemIdCompactData
keyData
keyInfo
keyName
xl_multi_insert_tuple
xl_multi_insert_tuple
xl_tdeheap_confirm
xl_tdeheap_confirm
xl_tdeheap_delete
xl_tdeheap_delete
xl_tdeheap_freeze_page
xl_tdeheap_freeze_plan
xl_tdeheap_header
xl_tdeheap_header
xl_tdeheap_inplace
xl_tdeheap_inplace
xl_tdeheap_insert
xl_tdeheap_insert
xl_tdeheap_lock
xl_tdeheap_lock
xl_tdeheap_lock_updated
xl_tdeheap_lock_updated
xl_tdeheap_multi_insert
xl_tdeheap_multi_insert
xl_tdeheap_new_cid
xl_tdeheap_new_cid
xl_tdeheap_prune
xl_tdeheap_prune
xl_tdeheap_rewrite_mapping
xl_tdeheap_rewrite_mapping
xl_tdeheap_truncate
xl_tdeheap_truncate
xl_tdeheap_update
xl_tdeheap_update
xl_tdeheap_vacuum
xl_tdeheap_visible
xl_tdeheap_visible
xlhp_freeze_plan
xlhp_freeze_plans
xlhp_prune_items
Loading…
Cancel
Save