summaryrefslogtreecommitdiffstats
path: root/module/zfs
diff options
context:
space:
mode:
authorNed Bass <[email protected]>2015-05-28 16:14:19 -0700
committerBrian Behlendorf <[email protected]>2015-06-05 12:41:17 -0700
commit5f8e1e850522ee5cd37366427da4b4101a71c8a8 (patch)
treee2d658d906c9287d008bc769ca7321e521d93f19 /module/zfs
parentd617648c7fc6904261f3ae8f2e3726c5c1838508 (diff)
dmu_objset_userquota_get_ids uses dn_bonus unsafely
The function dmu_objset_userquota_get_ids() checks and uses dn->dn_bonus outside of dn_struct_rwlock. If the dnode is being freed then the bonus dbuf may be in the process of getting evicted. In this case there is a race that may cause dmu_objset_userquota_get_ids() to access the dbuf after it has been destroyed. To prevent this, ensure that when we are using the bonus dbuf we are either holding a reference on it or have taken dn_struct_rwlock. Signed-off-by: Ned Bass <[email protected]> Signed-off-by: Brian Behlendorf <[email protected]> Closes #3443
Diffstat (limited to 'module/zfs')
-rw-r--r--module/zfs/dmu_objset.c20
1 files changed, 17 insertions, 3 deletions
diff --git a/module/zfs/dmu_objset.c b/module/zfs/dmu_objset.c
index ae4e1dd21..888c251ce 100644
--- a/module/zfs/dmu_objset.c
+++ b/module/zfs/dmu_objset.c
@@ -1314,6 +1314,7 @@ dmu_objset_userquota_get_ids(dnode_t *dn, boolean_t before, dmu_tx_t *tx)
int flags = dn->dn_id_flags;
int error;
boolean_t have_spill = B_FALSE;
+ boolean_t have_bonus = B_FALSE;
if (!dmu_objset_userused_enabled(dn->dn_objset))
return;
@@ -1325,8 +1326,21 @@ dmu_objset_userquota_get_ids(dnode_t *dn, boolean_t before, dmu_tx_t *tx)
if (before && dn->dn_bonuslen != 0)
data = DN_BONUS(dn->dn_phys);
else if (!before && dn->dn_bonuslen != 0) {
- if (dn->dn_bonus) {
- db = dn->dn_bonus;
+ db = dn->dn_bonus;
+ if (db != NULL) {
+ if (!RW_WRITE_HELD(&dn->dn_struct_rwlock)) {
+ have_bonus = dbuf_try_add_ref((dmu_buf_t *)db,
+ dn->dn_objset, dn->dn_object,
+ DMU_BONUS_BLKID, FTAG);
+
+ /*
+ * The hold will fail if the buffer is
+ * being evicted due to unlink, in which
+ * case nothing needs to be done.
+ */
+ if (!have_bonus)
+ return;
+ }
mutex_enter(&db->db_mtx);
data = dmu_objset_userquota_find_data(db, tx);
} else {
@@ -1401,7 +1415,7 @@ dmu_objset_userquota_get_ids(dnode_t *dn, boolean_t before, dmu_tx_t *tx)
dn->dn_id_flags |= DN_ID_CHKED_BONUS;
}
mutex_exit(&dn->dn_mtx);
- if (have_spill)
+ if (have_spill || have_bonus)
dmu_buf_rele((dmu_buf_t *)db, FTAG);
}