]> granicus.if.org Git - zfs/commitdiff
Assert that a dnode's bonuslen never exceeds its recorded size
authorSerapheim Dimitropoulos <serapheim@delphix.com>
Thu, 15 Aug 2019 14:44:57 +0000 (07:44 -0700)
committerBrian Behlendorf <behlendorf1@llnl.gov>
Thu, 15 Aug 2019 14:44:57 +0000 (08:44 -0600)
This patch introduces an assertion that can catch pitfalls in
development where there is a mismatch between the size of
reads and writes between a *_phys structure and its respective
in-core structure when bonus buffers are used.

This debugging-aid should be complementary to the verification
done by ztest in ztest_verify_dnode_bt().

A side to this patch is that we now clear out any extra bytes
past a bonus buffer's new size when the buffer is shrinking.

Reviewed-by: Matt Ahrens <matt@delphix.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Tom Caputi <tcaputi@datto.com>
Signed-off-by: Serapheim Dimitropoulos <serapheim@delphix.com>
Closes #8348

module/zfs/dbuf.c
module/zfs/dnode.c

index 0518205f99068d8eceff2e4f327eecee13277452..f8f96c142e9f9c9d9319a32f43586708ba99e670 100644 (file)
@@ -3931,6 +3931,46 @@ dbuf_sync_indirect(dbuf_dirty_record_t *dr, dmu_tx_t *tx)
        zio_nowait(zio);
 }
 
+#ifdef ZFS_DEBUG
+/*
+ * Verify that the size of the data in our bonus buffer does not exceed
+ * its recorded size.
+ *
+ * The purpose of this verification is to catch any cases in development
+ * where the size of a phys structure (i.e space_map_phys_t) grows and,
+ * due to incorrect feature management, older pools expect to read more
+ * data even though they didn't actually write it to begin with.
+ *
+ * For a example, this would catch an error in the feature logic where we
+ * open an older pool and we expect to write the space map histogram of
+ * a space map with size SPACE_MAP_SIZE_V0.
+ */
+static void
+dbuf_sync_leaf_verify_bonus_dnode(dbuf_dirty_record_t *dr)
+{
+       dnode_t *dn = DB_DNODE(dr->dr_dbuf);
+
+       /*
+        * Encrypted bonus buffers can have data past their bonuslen.
+        * Skip the verification of these blocks.
+        */
+       if (DMU_OT_IS_ENCRYPTED(dn->dn_bonustype))
+               return;
+
+       uint16_t bonuslen = dn->dn_phys->dn_bonuslen;
+       uint16_t maxbonuslen = DN_SLOTS_TO_BONUSLEN(dn->dn_num_slots);
+       ASSERT3U(bonuslen, <=, maxbonuslen);
+
+       arc_buf_t *datap = dr->dt.dl.dr_data;
+       char *datap_end = ((char *)datap) + bonuslen;
+       char *datap_max = ((char *)datap) + maxbonuslen;
+
+       /* ensure that everything is zero after our data */
+       for (; datap_end < datap_max; datap_end++)
+               ASSERT(*datap_end == 0);
+}
+#endif
+
 /*
  * dbuf_sync_leaf() is called recursively from dbuf_sync_list() so it is
  * critical the we not allow the compiler to inline this function in to
@@ -4007,6 +4047,10 @@ dbuf_sync_leaf(dbuf_dirty_record_t *dr, dmu_tx_t *tx)
                    DN_MAX_BONUS_LEN(dn->dn_phys));
                DB_DNODE_EXIT(db);
 
+#ifdef ZFS_DEBUG
+               dbuf_sync_leaf_verify_bonus_dnode(dr);
+#endif
+
                if (*datap != db->db.db_data) {
                        int slots = DB_DNODE(db)->dn_num_slots;
                        int bonuslen = DN_SLOTS_TO_BONUSLEN(slots);
index c9ff43fa63313b5cbf88014aa206c62a565fdf5e..ef62d394a91972dc8081a0a3c1e9637c0a39a5cb 100644 (file)
@@ -390,6 +390,14 @@ dnode_setbonuslen(dnode_t *dn, int newsize, dmu_tx_t *tx)
        rw_enter(&dn->dn_struct_rwlock, RW_WRITER);
        ASSERT3U(newsize, <=, DN_SLOTS_TO_BONUSLEN(dn->dn_num_slots) -
            (dn->dn_nblkptr-1) * sizeof (blkptr_t));
+
+       if (newsize < dn->dn_bonuslen) {
+               /* clear any data after the end of the new size */
+               size_t diff = dn->dn_bonuslen - newsize;
+               char *data_end = ((char *)dn->dn_bonus->db.db_data) + newsize;
+               bzero(data_end, diff);
+       }
+
        dn->dn_bonuslen = newsize;
        if (newsize == 0)
                dn->dn_next_bonuslen[tx->tx_txg & TXG_MASK] = DN_ZERO_BONUSLEN;