diff --git a/documentation/docs/concepts/threat-model.md b/documentation/docs/concepts/threat-model.md index 0ca22c0..ba2f430 100644 --- a/documentation/docs/concepts/threat-model.md +++ b/documentation/docs/concepts/threat-model.md @@ -52,6 +52,15 @@ running server: confidentiality, not authentication. `open_pg_tde` does not currently detect deliberate modification of ciphertext on disk. Authenticated page encryption is a separate, larger initiative; see the design note on authenticated pages. +- **Unlogged table forks.** An unlogged table keeps an init fork that + PostgreSQL copies over the main fork to reset the table after a crash. That + reset is a raw file copy of encrypted bytes performed by the server outside + the storage hooks, so the init fork and the main fork must share the same XTS + tweak at each block position for the copy to decrypt afterward. As a result, + an attacker with the data files can tell which main-fork blocks are + byte-identical to the init-fork template. This is limited to unlogged tables, + reveals only block equality and not the contents of either fork, and does not + affect regular (logged) tables. - **Column-level protection from the server.** All encryption is at the storage layer, so the server necessarily decrypts data to operate on it. `open_pg_tde` does not provide client-side or column-level encryption that @@ -84,4 +93,5 @@ running server: | Privileged user on a running server | No | | Catalog metadata and statistics | No | | Tamper detection (integrity) | No | +| Block equality between an unlogged table's init and main forks | No | | Encryption key exposure via the catalog | Not applicable (keys are not in the catalog) | diff --git a/src/encryption/enc_tde.c b/src/encryption/enc_tde.c index 4bdf4a0..6feee2a 100644 --- a/src/encryption/enc_tde.c +++ b/src/encryption/enc_tde.c @@ -188,7 +188,18 @@ CalcBlockIv(ForkNumber forknum, BlockNumber bn, const unsigned char *base_iv, un { memset(iv, 0, TDE_BLOCK_IV_LEN); - /* The init fork is copied to the main fork so we must use the same IV */ + /* + * The init fork is copied to the main fork so we must use the same IV. + * + * PostgreSQL resets an unlogged relation after a crash by raw-copying the + * init fork file over the main fork (copy_file() in reinit.c), which the + * storage hooks never see. For that copy of already-encrypted bytes to + * decrypt as the main fork, the init fork must be encrypted with the main + * fork's tweak. This is intentional. The tradeoff is that the two forks + * share the XTS tweak at each block, so their ciphertext reveals which + * blocks are byte-identical; that limitation is documented in the threat + * model (unlogged table forks). + */ iv[7] = forknum == INIT_FORKNUM ? MAIN_FORKNUM : forknum; iv[12] = bn >> 24;