Print this page
4185 New hash algorithm support

@@ -20,14 +20,16 @@
  */
 /*
  * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2013 by Delphix. All rights reserved.
  * Copyright (c) 2013, Joyent, Inc. All rights reserved.
+ * Copyright 2013 Saso Kiselkov. All rights reserved.
  */
 
 #include <sys/zfs_context.h>
 #include <sys/spa.h>
+#include <sys/spa_impl.h>
 #include <sys/zio.h>
 #include <sys/zio_checksum.h>
 #include <sys/zil.h>
 #include <zfs_fletcher.h>
 

@@ -57,31 +59,76 @@
  *
  * When writing a block, we always checksum it with the latest-and-greatest
  * checksum function of the appropriate strength.  When reading a block,
  * we compare the expected checksum against the actual checksum, which we
  * compute via the checksum function specified by BP_GET_CHECKSUM(bp).
+ *
+ * SALTED CHECKSUMS
+ *
+ * To enable the use of non-cryptographically secure hash algorithms in
+ * dedup we introduce the notion of salted checksums (MACs, really). A salted
+ * checksum is fed both a random 256-bit value (the salt) and the data to be
+ * checksummed. This salt is kept secret (stored on the pool, but never shown
+ * to the user), thus even if an attacker knew of collision weaknesses in the
+ * hash algorithm, they won't be able to mount a known plaintext attack on
+ * the DDT, since the actual hash value cannot be known ahead of time. How
+ * the salt is used is algorithm-specific (some might simply prefix it to the
+ * data block, others might need to utilize a full-blown HMAC). On disk the
+ * salt is stored in a ZAP object in the MOS (DMU_POOL_CHECKSUM_SALT).
+ *
+ * CONTEXT TEMPLATES
+ *
+ * Some hashing algorithms need to perform a substantial amount of
+ * initialization work (e.g. salted checksums above may need to pre-hash the
+ * salt) before being able to process data. Performing this redundant work
+ * for each block would be very wasteful, so we instead allow a checksum
+ * algorithm to do the work once (the first time it's used) and then keep
+ * this pre-initialized context as a template inside the spa_t
+ * (spa_cksum_tmpls). If the zio_checksum_info_t contains non-NULL
+ * ci_tmpl_init and ci_tmpl_free callbacks, they are used to construct and
+ * destruct the pre-initialized checksum context. The pre-initialized
+ * context is then reused during each checksum invocation and passed to the
+ * checksum function.
  */
 
 /*ARGSUSED*/
 static void
-zio_checksum_off(const void *buf, uint64_t size, zio_cksum_t *zcp)
+zio_checksum_off(const void *buf, uint64_t size, const zio_cksum_salt_t *salt,
+    const void *ctx_template, zio_cksum_t *zcp)
 {
         ZIO_SET_CHECKSUM(zcp, 0, 0, 0, 0);
 }
 
 zio_checksum_info_t zio_checksum_table[ZIO_CHECKSUM_FUNCTIONS] = {
-        {{NULL,                 NULL},                  0, 0, 0, "inherit"},
-        {{NULL,                 NULL},                  0, 0, 0, "on"},
-        {{zio_checksum_off,     zio_checksum_off},      0, 0, 0, "off"},
-        {{zio_checksum_SHA256,  zio_checksum_SHA256},   1, 1, 0, "label"},
-        {{zio_checksum_SHA256,  zio_checksum_SHA256},   1, 1, 0, "gang_header"},
-        {{fletcher_2_native,    fletcher_2_byteswap},   0, 1, 0, "zilog"},
-        {{fletcher_2_native,    fletcher_2_byteswap},   0, 0, 0, "fletcher2"},
-        {{fletcher_4_native,    fletcher_4_byteswap},   1, 0, 0, "fletcher4"},
-        {{zio_checksum_SHA256,  zio_checksum_SHA256},   1, 0, 1, "sha256"},
-        {{fletcher_4_native,    fletcher_4_byteswap},   0, 1, 0, "zilog2"},
-        {{zio_checksum_off,     zio_checksum_off},      0, 0, 0, "noparity"},
+        {{NULL, NULL}, NULL, NULL, 0, 0, 0, 0, "inherit"},
+        {{NULL, NULL}, NULL, NULL, 0, 0, 0, 0, "on"},
+        {{zio_checksum_off,             zio_checksum_off},
+            NULL, NULL, 0, 0, 0, 0, "off"},
+        {{zio_checksum_SHA256,          zio_checksum_SHA256},
+            NULL, NULL, 1, 1, 0, 0, "label"},
+        {{zio_checksum_SHA256,          zio_checksum_SHA256},
+            NULL, NULL, 1, 1, 0, 0, "gang_header"},
+        {{fletcher_2_native,            fletcher_2_byteswap},
+            NULL, NULL, 0, 1, 0, 0, "zilog"},
+        {{fletcher_2_native,            fletcher_2_byteswap},
+            NULL, NULL, 0, 0, 0, 0, "fletcher2"},
+        {{fletcher_4_native,            fletcher_4_byteswap},
+            NULL, NULL, 1, 0, 0, 0, "fletcher4"},
+        {{zio_checksum_SHA256,          zio_checksum_SHA256},
+            NULL, NULL, 1, 0, 1, 0, "sha256"},
+        {{fletcher_4_native,            fletcher_4_byteswap},
+            NULL, NULL, 0, 1, 0, 0, "zilog2"},
+        {{zio_checksum_off,             zio_checksum_off},
+            NULL, NULL, 0, 0, 0, 0, "noparity"},
+        {{zio_checksum_SHA512_native, zio_checksum_SHA512_byteswap},
+            NULL, NULL, 1, 0, 1, 0, "sha512"},
+        {{zio_checksum_skein_native,    zio_checksum_skein_byteswap},
+            zio_checksum_skein_tmpl_init, zio_checksum_skein_tmpl_free,
+            1, 0, 1, 1, "skein"},
+        {{zio_checksum_edonr_native,    zio_checksum_edonr_byteswap},
+            zio_checksum_edonr_tmpl_init, zio_checksum_edonr_tmpl_free,
+            1, 0, 1, 1, "edonr"}
 };
 
 enum zio_checksum
 zio_checksum_select(enum zio_checksum child, enum zio_checksum parent)
 {

@@ -146,10 +193,29 @@
 {
         ZIO_SET_CHECKSUM(zcp, offset, 0, 0, 0);
 }
 
 /*
+ * Calls the template init function of a checksum which supports context
+ * templates and installs the template into the spa_t.
+ */
+static void
+zio_checksum_template_init(enum zio_checksum checksum, spa_t *spa)
+{
+        zio_checksum_info_t *ci = &zio_checksum_table[checksum];
+
+        VERIFY(ci->ci_tmpl_init != NULL && ci->ci_tmpl_free != NULL);
+        mutex_enter(&spa->spa_cksum_tmpls_lock);
+        if (spa->spa_cksum_tmpls[checksum] == NULL) {
+                spa->spa_cksum_tmpls[checksum] =
+                    ci->ci_tmpl_init(&spa->spa_cksum_salt);
+                VERIFY(spa->spa_cksum_tmpls[checksum] != NULL);
+        }
+        mutex_exit(&spa->spa_cksum_tmpls_lock);
+}
+
+/*
  * Generate the checksum.
  */
 void
 zio_checksum_compute(zio_t *zio, enum zio_checksum checksum,
         void *data, uint64_t size)

@@ -156,14 +222,18 @@
 {
         blkptr_t *bp = zio->io_bp;
         uint64_t offset = zio->io_offset;
         zio_checksum_info_t *ci = &zio_checksum_table[checksum];
         zio_cksum_t cksum;
+        spa_t *spa = zio->io_spa;
 
         ASSERT((uint_t)checksum < ZIO_CHECKSUM_FUNCTIONS);
         ASSERT(ci->ci_func[0] != NULL);
 
+        if (ci->ci_tmpl_init != NULL && spa->spa_cksum_tmpls[checksum] == NULL)
+                zio_checksum_template_init(checksum, spa);
+
         if (ci->ci_eck) {
                 zio_eck_t *eck;
 
                 if (checksum == ZIO_CHECKSUM_ZILOG2) {
                         zil_chain_t *zilc = data;

@@ -179,14 +249,16 @@
                 else if (checksum == ZIO_CHECKSUM_LABEL)
                         zio_checksum_label_verifier(&eck->zec_cksum, offset);
                 else
                         bp->blk_cksum = eck->zec_cksum;
                 eck->zec_magic = ZEC_MAGIC;
-                ci->ci_func[0](data, size, &cksum);
+                ci->ci_func[0](data, size, &spa->spa_cksum_salt,
+                    spa->spa_cksum_tmpls[checksum], &cksum);
                 eck->zec_cksum = cksum;
         } else {
-                ci->ci_func[0](data, size, &bp->blk_cksum);
+                ci->ci_func[0](data, size, &spa->spa_cksum_salt,
+                    spa->spa_cksum_tmpls[checksum], &bp->blk_cksum);
         }
 }
 
 int
 zio_checksum_error(zio_t *zio, zio_bad_cksum_t *info)

@@ -200,14 +272,18 @@
             (BP_IS_GANG(bp) ? SPA_GANGBLOCKSIZE : BP_GET_PSIZE(bp)));
         uint64_t offset = zio->io_offset;
         void *data = zio->io_data;
         zio_checksum_info_t *ci = &zio_checksum_table[checksum];
         zio_cksum_t actual_cksum, expected_cksum, verifier;
+        spa_t *spa = zio->io_spa;
 
         if (checksum >= ZIO_CHECKSUM_FUNCTIONS || ci->ci_func[0] == NULL)
                 return (SET_ERROR(EINVAL));
 
+        if (ci->ci_tmpl_init != NULL && spa->spa_cksum_tmpls[checksum] == NULL)
+                zio_checksum_template_init(checksum, spa);
+
         if (ci->ci_eck) {
                 zio_eck_t *eck;
 
                 if (checksum == ZIO_CHECKSUM_ZILOG2) {
                         zil_chain_t *zilc = data;

@@ -241,21 +317,23 @@
                 if (byteswap)
                         byteswap_uint64_array(&verifier, sizeof (zio_cksum_t));
 
                 expected_cksum = eck->zec_cksum;
                 eck->zec_cksum = verifier;
-                ci->ci_func[byteswap](data, size, &actual_cksum);
+                ci->ci_func[byteswap](data, size, &spa->spa_cksum_salt,
+                    spa->spa_cksum_tmpls[checksum], &actual_cksum);
                 eck->zec_cksum = expected_cksum;
 
                 if (byteswap)
                         byteswap_uint64_array(&expected_cksum,
                             sizeof (zio_cksum_t));
         } else {
                 ASSERT(!BP_IS_GANG(bp));
                 byteswap = BP_SHOULD_BYTESWAP(bp);
                 expected_cksum = bp->blk_cksum;
-                ci->ci_func[byteswap](data, size, &actual_cksum);
+                ci->ci_func[byteswap](data, size, &spa->spa_cksum_salt,
+                    spa->spa_cksum_tmpls[checksum], &actual_cksum);
         }
 
         info->zbc_expected = expected_cksum;
         info->zbc_actual = actual_cksum;
         info->zbc_checksum_name = ci->ci_name;

@@ -272,6 +350,25 @@
                 info->zbc_injected = 1;
                 return (error);
         }
 
         return (0);
+}
+
+/*
+ * Called by a spa_t that's about to be deallocated. This steps through
+ * all of the checksum context templates and deallocates any that were
+ * initialized using the algorithm-specific template init function.
+ */
+void
+zio_checksum_templates_free(spa_t *spa)
+{
+        for (int checksum = 0; checksum < ZIO_CHECKSUM_FUNCTIONS; checksum++) {
+                if (spa->spa_cksum_tmpls[checksum] != NULL) {
+                        zio_checksum_info_t *ci = &zio_checksum_table[checksum];
+
+                        VERIFY(ci->ci_tmpl_free != NULL);
+                        ci->ci_tmpl_free(spa->spa_cksum_tmpls[checksum]);
+                        spa->spa_cksum_tmpls[checksum] = NULL;
+                }
+        }
 }