summaryrefslogtreecommitdiffstats
path: root/module/zfs
diff options
context:
space:
mode:
authorBrian Behlendorf <[email protected]>2014-09-10 11:59:03 -0700
committerBrian Behlendorf <[email protected]>2014-10-23 09:20:52 -0700
commit5f6d0b6f5aa9af2ee5be74ac415a574b732c2c0f (patch)
treedf5f04a48563bf0d5f26cce993be2439175b83bb /module/zfs
parentbc151f7b312dea09c6ec5b9a320e65140789643a (diff)
Handle block pointers with a corrupt logical size
The general strategy used by ZFS to verify that blocks are valid is to checksum everything. This has the advantage of being extremely robust and generically applicable regardless of the contents of the block. If a blocks checksum is valid then its contents are trusted by the higher layers. This system works exceptionally well as long as bad data is never written with a valid checksum. If this does somehow occur due to a software bug or a memory bit-flip on a non-ECC system it may result in kernel panic. One such place where this could occur is if somehow the logical size stored in a block pointer exceeds the maximum block size. This will result in an attempt to allocate a buffer greater than the maximum block size causing a system panic. To prevent this from happening the arc_read() function has been updated to detect this specific case. If a block pointer with an invalid logical size is passed it will treat the block as if it contained a checksum error. Signed-off-by: Tim Chase <[email protected]> Signed-off-by: Brian Behlendorf <[email protected]> Closes #2678
Diffstat (limited to 'module/zfs')
-rw-r--r--module/zfs/arc.c24
-rw-r--r--module/zfs/dbuf.c18
2 files changed, 31 insertions, 11 deletions
diff --git a/module/zfs/arc.c b/module/zfs/arc.c
index 2c7abe6ec..b42bb4050 100644
--- a/module/zfs/arc.c
+++ b/module/zfs/arc.c
@@ -1434,12 +1434,12 @@ arc_space_return(uint64_t space, arc_space_type_t type)
}
arc_buf_t *
-arc_buf_alloc(spa_t *spa, int size, void *tag, arc_buf_contents_t type)
+arc_buf_alloc(spa_t *spa, uint64_t size, void *tag, arc_buf_contents_t type)
{
arc_buf_hdr_t *hdr;
arc_buf_t *buf;
- ASSERT3U(size, >, 0);
+ VERIFY3U(size, <=, SPA_MAXBLOCKSIZE);
hdr = kmem_cache_alloc(hdr_cache, KM_PUSHPAGE);
ASSERT(BUF_EMPTY(hdr));
hdr->b_size = size;
@@ -1477,7 +1477,7 @@ static char *arc_onloan_tag = "onloan";
* freed.
*/
arc_buf_t *
-arc_loan_buf(spa_t *spa, int size)
+arc_loan_buf(spa_t *spa, uint64_t size)
{
arc_buf_t *buf;
@@ -1837,7 +1837,7 @@ arc_buf_remove_ref(arc_buf_t *buf, void* tag)
return (no_callback);
}
-int
+uint64_t
arc_buf_size(arc_buf_t *buf)
{
return (buf->b_hdr->b_size);
@@ -3307,6 +3307,22 @@ top:
enum zio_compress b_compress = ZIO_COMPRESS_OFF;
uint64_t b_asize = 0;
+ /*
+ * Gracefully handle a damaged logical block size as a
+ * checksum error by passing a dummy zio to the done callback.
+ */
+ if (size > SPA_MAXBLOCKSIZE) {
+ if (done) {
+ rzio = zio_null(pio, spa, NULL,
+ NULL, NULL, zio_flags);
+ rzio->io_error = ECKSUM;
+ done(rzio, buf, private);
+ zio_nowait(rzio);
+ }
+ rc = ECKSUM;
+ goto out;
+ }
+
if (hdr == NULL) {
/* this block is not in the cache */
arc_buf_hdr_t *exists = NULL;
diff --git a/module/zfs/dbuf.c b/module/zfs/dbuf.c
index 76a8a99ab..b31957094 100644
--- a/module/zfs/dbuf.c
+++ b/module/zfs/dbuf.c
@@ -571,12 +571,13 @@ dbuf_read_done(zio_t *zio, arc_buf_t *buf, void *vdb)
dbuf_rele_and_unlock(db, NULL);
}
-static void
+static int
dbuf_read_impl(dmu_buf_impl_t *db, zio_t *zio, uint32_t *flags)
{
dnode_t *dn;
zbookmark_phys_t zb;
uint32_t aflags = ARC_NOWAIT;
+ int err;
DB_DNODE_ENTER(db);
dn = DB_DNODE(db);
@@ -601,7 +602,7 @@ dbuf_read_impl(dmu_buf_impl_t *db, zio_t *zio, uint32_t *flags)
dbuf_update_data(db);
db->db_state = DB_CACHED;
mutex_exit(&db->db_mtx);
- return;
+ return (0);
}
/*
@@ -621,7 +622,7 @@ dbuf_read_impl(dmu_buf_impl_t *db, zio_t *zio, uint32_t *flags)
db->db_state = DB_CACHED;
*flags |= DB_RF_CACHED;
mutex_exit(&db->db_mtx);
- return;
+ return (0);
}
DB_DNODE_EXIT(db);
@@ -640,12 +641,14 @@ dbuf_read_impl(dmu_buf_impl_t *db, zio_t *zio, uint32_t *flags)
dbuf_add_ref(db, NULL);
- (void) arc_read(zio, db->db_objset->os_spa, db->db_blkptr,
+ err = arc_read(zio, db->db_objset->os_spa, db->db_blkptr,
dbuf_read_done, db, ZIO_PRIORITY_SYNC_READ,
(*flags & DB_RF_CANFAIL) ? ZIO_FLAG_CANFAIL : ZIO_FLAG_MUSTSUCCEED,
&aflags, &zb);
if (aflags & ARC_CACHED)
*flags |= DB_RF_CACHED;
+
+ return (SET_ERROR(err));
}
int
@@ -688,11 +691,12 @@ dbuf_read(dmu_buf_impl_t *db, zio_t *zio, uint32_t flags)
if (zio == NULL)
zio = zio_root(spa, NULL, NULL, ZIO_FLAG_CANFAIL);
- dbuf_read_impl(db, zio, &flags);
+
+ err = dbuf_read_impl(db, zio, &flags);
/* dbuf_read_impl has dropped db_mtx for us */
- if (prefetch)
+ if (!err && prefetch)
dmu_zfetch(&dn->dn_zfetch, db->db.db_offset,
db->db.db_size, flags & DB_RF_CACHED);
@@ -700,7 +704,7 @@ dbuf_read(dmu_buf_impl_t *db, zio_t *zio, uint32_t flags)
rw_exit(&dn->dn_struct_rwlock);
DB_DNODE_EXIT(db);
- if (!havepzio)
+ if (!err && !havepzio)
err = zio_wait(zio);
} else {
/*