Room 12
Compression, dedup, encryption
Between the application and the disk, three features can change a block. Each saves something or protects something, and each has a price that the code pays on every write.
Compression saves space, but compressing costs CPU, and a block that shrinks by a few bytes saves nothing once it is rounded up to whole sectors. Dedup avoids writing a block that is already on disk, but to know that it must look every written block up in a table, keep that table on disk and update it. Encryption hides the data, but the pool must still be able to check, repair and send blocks for which it may not have the key. The code answers each problem with one rule, and this room takes the three rules one by one. In the write pipeline they run in this order: compression, then encryption, then the checksum of what will be written, then the dedup lookup by that checksum.
1 Compression: save an eighth, or store as is
The compressor is given a limit: seven eighths of the block, rounded down to a whole number of sectors. If its output does not fit, the block is stored uncompressed. Two cases never reach the compressor or never reach the disk: a block of zeros becomes a hole, and an output of 112 bytes or less is folded into the block pointer itself. Choose the data, the block size, the sector size and the compression, and follow the branch that decides.
- Compression is LZ4: the block goes through the branches below. code
- The block is not all zeros. code
- The output (49,807 bytes) fits under the limit (112 KiB). code
- More than 112 bytes: it cannot go into the block pointer. code
- Rounded up to the sector: 52 KiB, less than 128 KiB. Stored compressed, the tail zeroed. code (this branch decided)
Stored compressed. 49,807 bytes compressed with LZ4, rounded up to the sector: PSIZE 52 KiB, 13 sectors at ashift 12.
Block pointer: PSIZE 52 KiB, LSIZE 128 KiB, compression LZ4; ASIZE 52 KiB.
| Word | Holds |
|---|---|
| 0 | DVA 1: vdev, ASIZE |
| 1 | DVA 1: G, offset |
| 2 | DVA 2: vdev, ASIZE |
| 3 | DVA 2: G, offset |
| 4 | DVA 3: vdev, ASIZE |
| 5 | DVA 3: G, offset |
| 6 | B D X, level, type, checksum, E, compression, PSIZE, LSIZE |
| 7 | R, padding |
| 8 | padding |
| 9 | physical birth txg |
| a | logical birth txg |
| b | fill count |
| c | checksum[0] |
| d | checksum[1] |
| e | checksum[2] |
| f | checksum[3] |
| ashift | Limit | Outcome | PSIZE | Allocated |
|---|---|---|---|---|
| 9 (512 bytes) | 114,688 | Stored compressed. | 50,176 | 50,176 |
| 12 (4 KiB) | 114,688 | Stored compressed. | 53,248 | 53,248 |
| 13 (8 KiB) | 114,688 | Stored compressed. | 57,344 | 57,344 |
Text version of this figure
49,807 bytes compressed with LZ4, rounded up to the sector: PSIZE 52 KiB, 13 sectors at ashift 12.
2 Dedup: a table looked up on every write
With dedup=on, every block written is looked up in the dedup table (DDT) by its checksum. If the
table already has it, the new block pointer is filled with the existing copy’s address and a reference count
goes up: nothing is written. If not, the block is written and a new entry records where. Write blocks, write
the same one again, then put the table over its quota.
3 writes3 DDT lookups2 data blocks written2 entries
The last write
Dedup table (DDT)
| Key (checksum) | Data | Refcount | DVA | Physical birth |
|---|---|---|---|---|
5cf94b00… | A | 2 | 0:400000 | 100 |
fa9a4a00… | B | 1 | 0:420000 | 101 |
Block pointers written
- bp0AD=1
0:400000born 100, physical 100 - bp1BD=1
0:420000born 101, physical 101 - bp2AD=1
0:400000born 102, physical 100
Data blocks on disk
0:400000Areferenced by bp0, bp20:420000Breferenced by bp1
Text version of this figure
- Miss: A is written at
0:400000; the entry gets the DVA and refcount 1. - Miss: B is written at
0:420000; the entry gets the DVA and refcount 1. - Hit: the new block pointer gets
0:400000from the entry, born in txg 102 with physical birth 100; refcount 2. Nothing is written.
3 writes, 3 DDT lookups, 2 data blocks written, 2 entries.
What it costs. The figure counts one DDT lookup per write, hit or miss: a dataset with
dedup=on pays it for every block, not only for duplicates. Each entry is a key of
40 bytes (the 256-bit checksum and one word of sizes and flags) and a value of
256 bytes in the traditional format, 72 in the newer flat one. The table lives on disk as
ZAP objects, and the code says why updating it is expensive: “each entry only takes up a tiny portion of a ZAP
leaf node, and so required reading the whole node, updating the entry, and writing it back. On busy pools,
this could add serious IO and memory overheads.” The fast_dedup feature answers with a log:
modified entries are appended to an on-disk log and kept in memory, and flushed to the ZAP a little every
txg. Only tables created with the feature get the log; a table created before it
keeps the rewrite of a ZAP leaf per txg. A dedup_table_quota can cap the table’s size on disk;
over it, new blocks are simply not deduplicated. Its default, auto, sets no
size and caps the table only on a pool with a dedup or special vdev: on a pool
of plain disks, the table grows with the data. The code gives no figure for memory per terabyte or for a dedup ratio: those depend on the
data, and this room does not invent them.
3 Encryption: secrets in the block pointer, checksums in clear
An encrypted block needs a salt, an IV and a MAC, and they travel where the block is referenced: in its block pointer, in the place of the third copy’s address and of half the checksum. The other half of the checksum is computed over the ciphertext, so the pool can check every block without the key. Compare the words of four kinds of block pointer, then unload the key of an encrypted file and scrub it.
A level-0 block of an encrypted type: the third DVA holds the salt and the start of the IV, the top of the fill count the rest of the IV, half of the checksum the MAC. At most 2 copies. code
| Word | Holds |
|---|---|
| 0 | DVA 1: vdev, ASIZE |
| 1 | DVA 1: G, offset |
| 2 | DVA 2: vdev, ASIZE |
| 3 | DVA 2: G, offset |
| 4 | salt (changed) |
| 5 | IV1: first 64 bits of the IV (changed) |
| 6 | B D X, level, type, checksum, E, compression, PSIZE, LSIZE |
| 7 | R, padding |
| 8 | padding |
| 9 | physical birth txg |
| a | logical birth txg |
| b | IV2: last 32 bits of the IV | fill count, 32 bits (changed) |
| c | checksum[0] |
| d | checksum[1] |
| e | MAC[0] (changed) |
| f | MAC[1] (changed) |
An encrypted file
Key loaded: the encrypted blocks are decrypted on read.
- dnode blockencrypted type, core in cleardnode core, in clear: type, levels, block pointerbonus buffer: “file attributes”
- indirect block (L1)indirect, checksum of the MACsthree block pointers, in clear: DVAs, births, checksums, salts, IVs, MACs
- data block 0encrypted“This text”
- data block 1encrypted“stays readable”
- data block 2encrypted“with the key.”
Text version of this figure
An ordinary block pointer: three DVAs, a 256-bit checksum. At most 3 copies.
A level-0 block of an encrypted type: the third DVA holds the salt and the start of the IV, the top of the fill count the rest of the IV, half of the checksum the MAC. At most 2 copies.
A level-0 block of a type that must stay readable: its data is in clear, protected by a MAC in half of the checksum. At most 3 copies.
An indirect block of an encrypted dataset: in clear, its MAC words hold a checksum of the MACs of the level below. At most 3 copies.
Key unloaded: the data blocks and the bonus buffer read as ciphertext; the dnode core and the indirect block are in clear on disk.
The idea, at three depths
In one sentence
Compression keeps a block compressed only if it saves at least an eighth, counted in whole sectors; dedup replaces a write with a reference when the block’s checksum is already in its table, at the price of a lookup on every write; encryption puts its salt, IV and MAC in the block pointer and leaves a truncated checksum of the ciphertext, so scrub needs no key.
In one paragraph
In zio_write_compress, an all-zero block becomes a hole; otherwise the compressor gets a limit of
seven eighths of the block, snapped down to the sector, and an output that does not fit leaves the block
uncompressed; an output of at most 112 bytes goes into the block pointer, which then holds data instead of
addresses; anything else is rounded up to whole sectors. Then, in an encrypted dataset, encryption uses the
third DVA for the salt and the start of the IV, the top of the fill count for the rest of the IV, and half of
the checksum for the MAC; only level-0 blocks of encrypted types are encrypted. The checksum comes next,
computed over what will be written, ciphertext included, and scrub, which reads blocks raw, checks its
remaining 128 bits. Last, with dedup, zio_ddt_write looks that checksum up in the DDT: a hit
copies the entry’s DVA into the new block pointer and adds a reference, with no write; a miss writes the block
and records its DVA in a new entry; a table over its quota refuses new entries, and the block is written
without its D bit.
In detail
The limit. Before compressing, the write path asks how big the output may be.
zio_get_compression_max_size
Excerpt of module/zfs/zio.c, lines
1791–1809, OpenZFS zfs-2.4.4.
View on GitHub
size_t
zio_get_compression_max_size(enum zio_compress compress, uint64_t gcd_alloc,
uint64_t min_alloc, size_t s_len)
{
size_t d_len;
/* minimum 12.5% must be saved (legacy value, may be changed later) */
d_len = s_len - (s_len >> 3);
/* ZLE can't use exactly d_len bytes, it needs more, so ignore it */
if (compress == ZIO_COMPRESS_ZLE)
return (d_len);
d_len = d_len - d_len % gcd_alloc;
if (d_len < min_alloc)
return (BPE_PAYLOAD_SIZE);
return (d_len);
}- L1797–L1798 Seven eighths of the source: at least 12.5% must be saved, a “legacy value”, says the comment.
- L1800–L1802 ZLE, which only compresses runs of zeros, is exempt from what follows.
-
L1804
Rounded down to a multiple of
spa_gcd_alloc, which for a pool of plain disks of one ashift is the sector (the greatest common divisor of the vdevs’ allocation sizes): a saving smaller than a sector saves nothing. -
L1806–L1807
If that leaves less than one sector, the only output worth having is one that fits in a block pointer:
BPE_PAYLOAD_SIZE, 14 words of 8 bytes, 112 bytes.
Seven eighths of 128 KiB is 112 KiB, already a multiple of 512 bytes, 4 KiB and 8 KiB: for the default record size the snap changes nothing. It matters for small blocks: at ashift 12, seven eighths of 8 KiB snaps down to 4 KiB, and seven eighths of 4 KiB snaps to nothing, so a 4 KiB block on 4 KiB sectors is compressed only if it shrinks to 112 bytes. The figure’s table shows the same data at the three sector sizes.
The four outcomes.
zio_write_compress
Excerpt of module/zfs/zio.c, lines
1982–2017, OpenZFS zfs-2.4.4.
View on GitHub
/* If it's a compressed write that is not raw, compress the buffer. */
if (compress != ZIO_COMPRESS_OFF &&
!(zio->io_flags & ZIO_FLAG_RAW_COMPRESS)) {
abd_t *cabd = NULL;
if (abd_cmp_zero(zio->io_abd, lsize) == 0)
psize = 0;
else if (compress == ZIO_COMPRESS_EMPTY)
psize = lsize;
else
psize = zio_compress_data(compress, zio->io_abd, &cabd,
lsize,
zio_get_compression_max_size(compress,
spa->spa_gcd_alloc, spa->spa_min_alloc, lsize),
zp->zp_complevel);
if (psize == 0) {
compress = ZIO_COMPRESS_OFF;
} else if (psize >= lsize) {
compress = ZIO_COMPRESS_OFF;
if (cabd != NULL)
abd_free(cabd);
} else if (psize <= BPE_PAYLOAD_SIZE && !zp->zp_encrypt &&
zp->zp_level == 0 && !DMU_OT_HAS_FILL(zp->zp_type) &&
spa_feature_is_enabled(spa, SPA_FEATURE_EMBEDDED_DATA)) {
void *cbuf = abd_borrow_buf_copy(cabd, lsize);
encode_embedded_bp_compressed(bp,
cbuf, compress, lsize, psize);
BPE_SET_ETYPE(bp, BP_EMBEDDED_TYPE_DATA);
BP_SET_TYPE(bp, zio->io_prop.zp_type);
BP_SET_LEVEL(bp, zio->io_prop.zp_level);
abd_return_buf(cabd, cbuf, lsize);
abd_free(cabd);
BP_SET_LOGICAL_BIRTH(bp, zio->io_txg);
zio->io_pipeline = ZIO_INTERLOCK_PIPELINE;
ASSERT(spa_feature_is_active(spa,
SPA_FEATURE_EMBEDDED_DATA));
return (zio);- L1983–L1984 None of this runs with compression off: a block of zeros is then written like any other, unless dedup is on, which turns zero detection on because “storing many references to an all zeros block in the dedup table would be expensive”.
- L1986–L1987 All zeros: PSIZE 0, which becomes a hole below.
-
L1991–L1995
The compressor runs with the limit as its output size;
zio_compress_datareturns the source size when the output does not fit. - L1996–L1997 A hole: compression is marked off, and PSIZE stays 0.
- L1998–L2001 An output that saved nothing: compression is off for this block, and it is stored at its logical size.
-
L2002–L2017
At most 112 bytes, not encrypted, at level 0, of a type without a fill count, with
embedded_dataenabled: the data is encoded into the block pointer, whose words then hold payload, and the write stops there, with no allocation.
zio_write_compress
Excerpt of module/zfs/zio.c, lines
2018–2039, OpenZFS zfs-2.4.4.
View on GitHub
} else {
/*
* Round compressed size up to the minimum allocation
* size of the smallest-ashift device, and zero the
* tail. This ensures that the compressed size of the
* BP (and thus compressratio property) are correct,
* in that we charge for the padding used to fill out
* the last sector.
*/
size_t rounded = (size_t)zio_roundup_alloc_size(spa,
psize);
if (rounded >= lsize) {
compress = ZIO_COMPRESS_OFF;
abd_free(cabd);
psize = lsize;
} else {
abd_zero_off(cabd, psize, rounded - psize);
psize = rounded;
zio_push_transform(zio, cabd,
psize, lsize, NULL);
}
}-
L2027–L2028
zio_roundup_alloc_sizerounds up to the minimum allocation, or to a multiple of the sector above it. - L2029–L2032 If that is not smaller than the block, it is stored uncompressed. The snapped limit makes this rare; ZLE, unsnapped, reaches it, and so does a 112-byte output of an encrypted block, which cannot be embedded.
-
L2034–L2035
The tail of the last sector is zeroed and PSIZE is the rounded size, so that “the compressed size of the BP (and thus compressratio property) are correct”. A hole allocates nothing; if the block existed before, it keeps its size, type and level, and is born in this txg (
hole_birth), so an incremental send sees it; other blocks get PSIZE, compression, checksum and the D bit.
On a single disk, the space allocated is PSIZE rounded up to the sector: a block is charged whole sectors, compressed or not, which is why the limit counts in sectors.
Dedup: the lookup. A block with the D bit goes through zio_ddt_write.
zio_ddt_write
Excerpt of module/zfs/zio.c, lines
3816–3832, OpenZFS zfs-2.4.4.
View on GitHub
ddt_enter(ddt);
/*
* Search DDT for matching entry. Skip DVAs verification here, since
* they can go only from override, and once we get here the override
* pointer can't have "D" flag to be confused with pruned DDT entries.
*/
IMPLY(zio->io_bp_override, !BP_GET_DEDUP(zio->io_bp_override));
dde = ddt_lookup(ddt, bp, B_FALSE);
if (dde == NULL) {
/* DDT size is over its quota so no new entries */
ddt_exit(ddt);
zp->zp_dedup = B_FALSE;
BP_SET_DEDUP(bp, B_FALSE);
if (zio->io_bp_override == NULL)
zio->io_pipeline = ZIO_WRITE_PIPELINE;
return (zio);
}- L3823 Every dedup write looks its block pointer up, under the DDT’s lock.
- L3824–L3832 NULL: the table is over its quota. The D bit is cleared and the block goes through the ordinary write pipeline, not deduplicated.
ddt_lookup
Excerpt of module/zfs/ddt.c, lines
1374–1387, OpenZFS zfs-2.4.4.
View on GitHub
boolean_t valid = B_TRUE;
if (dde->dde_type == DDT_TYPES &&
dde->dde_class == DDT_CLASSES &&
ddt_over_quota(spa)) {
/* Over quota. If no one is waiting, clean up right now. */
if (dde->dde_waiters == 0) {
avl_remove(&ddt->ddt_tree, dde);
ddt_free(ddt, dde);
return (NULL);
}
/* Flag cleanup required */
dde->dde_flags |= DDE_FLAG_OVERQUOTA;-
L1376–L1384
NULL comes from here: the key is in no table (a block never seen) and
ddt_over_quotasays the table is too big. A key that is found is still returned: over quota, a duplicate is still deduplicated; only new entries are refused.
A hit. An entry with enough DVAs for the block’s copies:
zio_ddt_write
Excerpt of module/zfs/zio.c, lines
3958–3987, OpenZFS zfs-2.4.4.
View on GitHub
/*
* If there are enough DVAs in the entry to service our request,
* then we can just use them as-is.
*/
if (have_dvas >= need_dvas) {
if (dde_io != NULL)
mutex_exit(&dde_io->dde_io_lock);
/*
* For rewrite operations, try preserving the original
* logical birth time. If the result matches the
* original BP, this becomes a NOP.
*/
if (zp->zp_rewrite) {
uint64_t orig_logical_birth =
BP_GET_LOGICAL_BIRTH(&zio->io_bp_orig);
ddt_bp_fill(ddp, v, bp, orig_logical_birth);
if (BP_EQUAL(bp, &zio->io_bp_orig)) {
/* We can skip accounting. */
ddt_exit(ddt);
zio->io_flags |= ZIO_FLAG_NOPWRITE;
return (zio);
}
}
ddt_bp_fill(ddp, v, bp, txg);
ddt_phys_addref(ddp, v);
ddt_exit(ddt);
return (zio);
}- L3962 The entry holds as many DVAs as the write needs.
- L3971–L3981 A rewrite keeps its original birth if that gives the same block pointer: then nothing at all changes.
-
L3983–L3986
ddt_bp_fillcopies the entry’s DVAs into the new block pointer, with the current txg as logical birth and the entry’s as physical birth;ddt_phys_addrefadds a reference; the function returns with no write I/O.
The two births are why the figure shows “born 102, physical 100”: the data was written in txg 100, and the block pointer created in txg 102. The send / receive room sends by logical birth, so a block deduplicated after a snapshot is still sent.
A miss. A new entry has no DVA: the hit branch is not taken, and a child write is issued with the entry attached.
zio_ddt_write
Excerpt of module/zfs/zio.c, lines
4156–4167, OpenZFS zfs-2.4.4.
View on GitHub
/*
* Optimistically increment the refcount for this dedup write.
* If the write fails, zio_ddt_child_write_done() will decrement
* for all non-DDT-child parents.
*/
ddt_phys_addref(ddp, v);
ddt_exit(ddt);
zio_nowait(cio);
return (zio);- L4161 The new entry’s refcount goes from 0 to 1 now, before the write completes; it is undone if the write fails.
-
L4165
The child write runs; when it has its DVA,
zio_ddt_child_write_readycopies it into the entry, for the next hit.
The table on disk. The entries modified in a txg must reach the DDT’s storage objects.
DDT: the dedup log
Excerpt of module/zfs/ddt.c, lines
136–156, OpenZFS zfs-2.4.4.
View on GitHub
* ## Dedup log
*
* Historically, all entries modified on a txg were written back to dedup
* storage objects at the end of every txg. This could cause significant
* overheads, as each entry only takes up a tiny portion of a ZAP leaf node,
* and so required reading the whole node, updating the entry, and writing it
* back. On busy pools, this could add serious IO and memory overheads.
*
* To address this, the dedup log was added. If the "fast_dedup" feature is
* enabled, at the end of each txg, modified entries will be copied to an
* in-memory "log" object (ddt_log_t), and appended to an on-disk log. If the
* same block is requested again, the in-memory object will be checked first,
* and if its there, the entry inflated back onto the live tree without going
* to storage. The on-disk log is only read at pool import time, to reload the
* in-memory log.
*
* Each txg, some amount of the in-memory log will be flushed out to a DDT
* storage object (ie ZAP) as normal. OpenZFS will try hard to flush enough to
* keep up with the rate of change on dedup entries, but not so much that it
* would impact overall throughput, and not using too much memory. See the
* zfs_dedup_log_* tunables in zfs(4) for more details.- L138–L142 Without the log, every modified entry was written back at the end of its txg, and an entry is a tiny part of a ZAP leaf: reading the whole leaf, updating one entry, writing the leaf back.
-
L144–L150
With
fast_dedup, on a table created with it (only those getDDT_FLAG_LOG), modified entries go to an in-memory log and are appended to an on-disk log, read back only at import. By default the logs are swapped after 8 txgs at most. -
L152–L156
Some of the log is flushed to the ZAP every txg, as much as keeps up without hurting throughput, as the
zfs_dedup_log_*tunables decide, “and not using too much memory”. The comment gives no number here.
Two more mechanisms complete the picture, in the same comment: pruning removes old entries that were never duplicated, to make room under the quota, and scrub walks the DDT first, so that a block referenced many times is scrubbed once.
The encrypted block pointer.
blkptr_t, encrypted
Excerpt of include/sys/spa.h, lines
188–225, OpenZFS zfs-2.4.4.
View on GitHub
/*
* The blkptr_t's of encrypted blocks also need to store the encryption
* parameters so that the block can be decrypted. This layout is as follows:
*
* 64 56 48 40 32 24 16 8 0
* +-------+-------+-------+-------+-------+-------+-------+-------+
* 0 | pad | vdev1 | pad | ASIZE |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* 1 |G| offset1 |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* 2 | pad | vdev2 | pad | ASIZE |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* 3 |G| offset2 |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* 4 | salt |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* 5 | IV1 |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* 6 |BDX|lvl| type | cksum |E| comp| PSIZE | LSIZE |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* 7 |R| padding |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* 8 | padding |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* 9 | physical birth txg |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* a | logical birth txg |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* b | IV2 | fill count |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* c | checksum[0] |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* d | checksum[1] |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* e | MAC[0] |
* +-------+-------+-------+-------+-------+-------+-------+-------+
* f | MAC[1] |
* +-------+-------+-------+-------+-------+-------+-------+-------+- L194–L201 The first two DVAs, as in any block pointer: an encrypted block keeps at most two copies.
-
L202–L205
The third DVA’s two words hold the salt and IV1, the first 64 bits of the 96-bit IV (
zio_crypt_encode_params_bp). - L216 IV2, the last 32 bits of the IV, in the upper half of the fill count, which an encrypted level-0 block does not need.
-
L218–L225
128 bits of checksum, then the 128-bit MAC (
zio_crypt_encode_mac_bp).
The X bit, bit 61 of word 6, has three meanings: on a level-0 block of an encrypted type it means encrypted; on a level-0 block of an unencrypted type, authenticated; on an indirect block, that the MAC words hold a checksum of the MACs below. An embedded block pointer cannot be encrypted: it has no words left for salt, IV and MAC. The cipher is AES-GCM or AES-CCM, with 128, 192 or 256-bit keys, authentication SHA512-HMAC, with an 8-byte salt, a 12-byte IV and a 16-byte MAC.
What each kind of block gets.
zio_encrypt
Excerpt of module/zfs/zio.c, lines
5123–5153, OpenZFS zfs-2.4.4.
View on GitHub
/* indirect blocks only maintain a cksum of the lower level MACs */
if (BP_GET_LEVEL(bp) > 0) {
BP_SET_CRYPT(bp, B_TRUE);
VERIFY0(zio_crypt_do_indirect_mac_checksum_abd(B_TRUE,
zio->io_orig_abd, BP_GET_LSIZE(bp), BP_SHOULD_BYTESWAP(bp),
mac));
zio_crypt_encode_mac_bp(bp, mac);
return (zio);
}
/*
* Objset blocks are a special case since they have 2 256-bit MACs
* embedded within them.
*/
if (ot == DMU_OT_OBJSET) {
ASSERT0(DMU_OT_IS_ENCRYPTED(ot));
ASSERT3U(BP_GET_COMPRESS(bp), ==, ZIO_COMPRESS_OFF);
BP_SET_CRYPT(bp, B_TRUE);
VERIFY0(spa_do_crypt_objset_mac_abd(B_TRUE, spa, dsobj,
zio->io_abd, psize, BP_SHOULD_BYTESWAP(bp)));
return (zio);
}
/* unencrypted object types are only authenticated with a MAC */
if (!DMU_OT_IS_ENCRYPTED(ot)) {
BP_SET_CRYPT(bp, B_TRUE);
VERIFY0(spa_do_crypt_mac_abd(B_TRUE, spa, dsobj,
zio->io_abd, psize, mac));
zio_crypt_encode_mac_bp(bp, mac);
return (zio);
}- L5123–L5131 An indirect block is not encrypted: its block pointer gets, in the MAC words, a checksum of the MACs of the blocks it points to.
- L5137–L5144 The objset block carries its two MACs inside itself.
- L5146–L5153 A level-0 block of an unencrypted type is only authenticated: a MAC, the data in clear. What is left is encrypted, and salt, IV and MAC are encoded in its block pointer.
dmu_write_policy
Excerpt of module/zfs/dmu.c, lines
2511–2535, OpenZFS zfs-2.4.4.
View on GitHub
/*
* All objects in an encrypted objset are protected from modification
* via a MAC. Encrypted objects store their IV and salt in the last DVA
* in the bp, so we cannot use all copies. Encrypted objects are also
* not subject to nopwrite since writing the same data will still
* result in a new ciphertext. Only encrypted blocks can be dedup'd
* to avoid ambiguity in the dedup code since the DDT does not store
* object types.
*/
if (os->os_encrypted && (wp & WP_NOFILL) == 0) {
encrypt = B_TRUE;
if (DMU_OT_IS_ENCRYPTED(type)) {
copies = MIN(copies, SPA_DVAS_PER_BP - 1);
gang_copies = MIN(gang_copies, SPA_DVAS_PER_BP - 1);
nopwrite = B_FALSE;
} else {
dedup = B_FALSE;
}
if (level <= 0 &&
(type == DMU_OT_DNODE || type == DMU_OT_OBJSET)) {
compress = ZIO_COMPRESS_EMPTY;
}
}- L2523–L2526 In an encrypted dataset, an encrypted type keeps at most two copies, since the third DVA holds salt and IV.
- L2527–L2529 Blocks of unencrypted types are not deduplicated: the DDT does not record object types.
What stays in clear, and why scrub needs no key.
zio_crypt.c, block comment
Excerpt of module/os/linux/zfs/zio_crypt.c, lines
85–120, OpenZFS zfs-2.4.4.
View on GitHub
* Message Authentication Code (MAC)
* The MAC is an output of authenticated encryption modes such as AES-GCM and
* AES-CCM. Its purpose is to ensure that an attacker cannot modify encrypted
* data on disk and return garbage to the application. Effectively, it is a
* checksum that can not be reproduced by an attacker. We store the MAC in the
* second 128 bits of blk_cksum, leaving the first 128 bits for a truncated
* regular checksum of the ciphertext which can be used for scrubbing.
*
* OBJECT AUTHENTICATION:
* Some object types, such as DMU_OT_MASTER_NODE cannot be encrypted because
* they contain some info that always needs to be readable. To prevent this
* data from being altered, we authenticate this data using SHA512-HMAC. This
* will produce a MAC (similar to the one produced via encryption) which can
* be used to verify the object was not modified. HMACs do not require key
* rotation or IVs, so we can keep up to the full 3 copies of authenticated
* data.
*
* ZIL ENCRYPTION:
* ZIL blocks have their bp written to disk ahead of the associated data, so we
* cannot store the MAC there as we normally do. For these blocks the MAC is
* stored in the embedded checksum within the zil_chain_t header. The salt and
* IV are generated for the block on bp allocation instead of at encryption
* time. In addition, ZIL blocks have some pieces that must be left in plaintext
* for claiming even though all of the sensitive user data still needs to be
* encrypted. The function zio_crypt_init_uios_zil() handles parsing which
* pieces of the block need to be encrypted. All data that is not encrypted is
* authenticated using the AAD mechanisms that the supported encryption modes
* provide for. In order to preserve the semantics of the ZIL for encrypted
* datasets, the ZIL is not protected at the objset level as described below.
*
* DNODE ENCRYPTION:
* Similarly to ZIL blocks, the core part of each dnode_phys_t needs to be left
* in plaintext for scrubbing and claiming, but the bonus buffers might contain
* sensitive user data. The function zio_crypt_init_uios_dnode() handles parsing
* which pieces of the block need to be encrypted. For more details about
* dnode authentication and encryption, see zio_crypt_init_uios_dnode().- L89–L91 The MAC takes the second half of the checksum, “leaving the first 128 bits for a truncated regular checksum of the ciphertext which can be used for scrubbing”.
- L94–L100 Object types that must stay readable, the master node among them, are only authenticated, and can keep up to three copies.
- L115–L120 The core of every dnode stays in plaintext “for scrubbing and claiming”; only the bonus buffers are encrypted.
zio_checksum_error_impl
Excerpt of module/zfs/zio_checksum.c, lines
508–526, OpenZFS zfs-2.4.4.
View on GitHub
/*
* MAC checksums are a special case since half of this checksum will
* actually be the encryption MAC. This will be verified by the
* decryption process, so we just check the truncated checksum now.
* Objset blocks use embedded MACs so we don't truncate the checksum
* for them.
*/
if (bp != NULL && BP_USES_CRYPT(bp) &&
BP_GET_TYPE(bp) != DMU_OT_OBJSET) {
if (!(ci->ci_flags & ZCHECKSUM_FLAG_DEDUP)) {
actual_cksum.zc_word[0] ^= actual_cksum.zc_word[2];
actual_cksum.zc_word[1] ^= actual_cksum.zc_word[3];
}
actual_cksum.zc_word[2] = 0;
actual_cksum.zc_word[3] = 0;
expected_cksum.zc_word[2] = 0;
expected_cksum.zc_word[3] = 0;
}- L515–L516 A block pointer with the X bit, except an objset block.
- L517–L520 For a checksum not made for dedup (fletcher-4, for instance), words 2 and 3 are folded into 0 and 1, as the writer did.
- L522–L525 Words 2 and 3 are ignored on both sides: the MAC is left to decryption.
Scrub reads with ZIO_FLAG_RAW, which skips decryption, so this check is
all it does: the figure’s scrub verifies every block with the key unloaded, and catches a flipped bit. A read
that must decrypt looks the key up and fails with EACCES when it is not
loaded. The key itself is a randomly generated master key, stored on disk
encrypted with the user’s wrapping key, so changing the passphrase re-encrypts no data; snapshots and
clones share it. Encrypted blocks can be deduplicated only if they have the same
ciphertext: with dedup, their salt and IV come from an HMAC of the plaintext instead of being random, and dedup works
only within a clone family.
What the figures leave out
The compressor’s output is an input of the figure, not computed by a real compressor: the code gives no ratio,
and the presets are examples. LZ4 stands for every compressor but ZLE; they all follow the same limit. Compression
levels are not shown, nor the rule that stops compressing rewrites from
sync pass 8 on. The pool is plain disks of one ashift, where the minimum allocation and
the sector are the same; raidz and mixed pools are not. Dedup writes one block per txg with
copies=1; dedup verify, collisions, more copies, the partial-DVA extension of fast dedup and the
log are told, not animated; DVAs are made up. The figure’s ciphertext, salt, IV and MAC are stand-ins, not AES;
the checksum is fletcher-4 of the figure’s bytes. Key rotation, which takes a new salt after
400,000,000 blocks, ZIL encryption and the objset MACs are not shown, and neither is any
throughput, memory or ratio figure: the code does not give one.