Skip to content

Commit

Permalink
Decryption error handling improvements
Browse files Browse the repository at this point in the history
Currently, the decryption and block authentication code in
the ZIO / ARC layers is a bit inconsistent with regards to
the ereports that are produces and the error codes that are
passed to calling functions. This patch ensures that all of
these errors (which begin as ECKSUM) are converted to EIO
before they leave the ZIO or ARC layer and that ereports
are correctly generated on each decryption / authentication
failure.

In addition, this patch fixes a bug in zio_decrypt() where
ECKSUM never gets written to zio->io_error.

Reviewed by: Matt Ahrens <matt@delphix.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: Tom Caputi <tcaputi@datto.com>
Closes #7372
  • Loading branch information
Tom Caputi authored and behlendorf committed Mar 31, 2018
1 parent 4515b1d commit a2c2ed1
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 23 deletions.
2 changes: 1 addition & 1 deletion include/sys/arc.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ boolean_t arc_is_unauthenticated(arc_buf_t *buf);
enum zio_compress arc_get_compression(arc_buf_t *buf);
void arc_get_raw_params(arc_buf_t *buf, boolean_t *byteorder, uint8_t *salt,
uint8_t *iv, uint8_t *mac);
int arc_untransform(arc_buf_t *buf, spa_t *spa, uint64_t dsobj,
int arc_untransform(arc_buf_t *buf, spa_t *spa, const zbookmark_phys_t *zb,
boolean_t in_place);
void arc_convert_to_raw(arc_buf_t *buf, uint64_t dsobj, boolean_t byteorder,
dmu_object_type_t ot, const uint8_t *salt, const uint8_t *iv,
Expand Down
3 changes: 2 additions & 1 deletion include/sys/spa.h
Original file line number Diff line number Diff line change
Expand Up @@ -1024,7 +1024,8 @@ extern void spa_history_log_internal_dd(dsl_dir_t *dd, const char *operation,
struct zbookmark_phys;
extern void spa_log_error(spa_t *spa, const zbookmark_phys_t *zb);
extern void zfs_ereport_post(const char *class, spa_t *spa, vdev_t *vd,
zbookmark_phys_t *zb, zio_t *zio, uint64_t stateoroffset, uint64_t length);
const zbookmark_phys_t *zb, zio_t *zio, uint64_t stateoroffset,
uint64_t length);
extern nvlist_t *zfs_event_create(spa_t *spa, vdev_t *vd, const char *type,
const char *name, nvlist_t *aux);
extern void zfs_post_remove(spa_t *spa, vdev_t *vd);
Expand Down
9 changes: 5 additions & 4 deletions include/sys/zio.h
Original file line number Diff line number Diff line change
Expand Up @@ -649,17 +649,18 @@ extern hrtime_t zio_handle_io_delay(zio_t *zio);
* Checksum ereport functions
*/
extern void zfs_ereport_start_checksum(spa_t *spa, vdev_t *vd,
zbookmark_phys_t *zb, struct zio *zio, uint64_t offset, uint64_t length,
void *arg, struct zio_bad_cksum *info);
const zbookmark_phys_t *zb, struct zio *zio, uint64_t offset,
uint64_t length, void *arg, struct zio_bad_cksum *info);
extern void zfs_ereport_finish_checksum(zio_cksum_report_t *report,
const abd_t *good_data, const abd_t *bad_data, boolean_t drop_if_identical);

extern void zfs_ereport_free_checksum(zio_cksum_report_t *report);

/* If we have the good data in hand, this function can be used */
extern void zfs_ereport_post_checksum(spa_t *spa, vdev_t *vd,
zbookmark_phys_t *zb, struct zio *zio, uint64_t offset, uint64_t length,
const abd_t *good_data, const abd_t *bad_data, struct zio_bad_cksum *info);
const zbookmark_phys_t *zb, struct zio *zio, uint64_t offset,
uint64_t length, const abd_t *good_data, const abd_t *bad_data,
struct zio_bad_cksum *info);

/* Called from spa_sync(), but primarily an injection handler */
extern void spa_handle_ignored_writes(spa_t *spa);
Expand Down
34 changes: 30 additions & 4 deletions module/zfs/arc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2260,14 +2260,27 @@ arc_buf_fill(arc_buf_t *buf, spa_t *spa, uint64_t dsobj, arc_fill_flags_t flags)
* callers.
*/
int
arc_untransform(arc_buf_t *buf, spa_t *spa, uint64_t dsobj, boolean_t in_place)
arc_untransform(arc_buf_t *buf, spa_t *spa, const zbookmark_phys_t *zb,
boolean_t in_place)
{
int ret;
arc_fill_flags_t flags = 0;

if (in_place)
flags |= ARC_FILL_IN_PLACE;

return (arc_buf_fill(buf, spa, dsobj, flags));
ret = arc_buf_fill(buf, spa, zb->zb_objset, flags);
if (ret == ECKSUM) {
/*
* Convert authentication and decryption errors to EIO
* (and generate an ereport) before leaving the ARC.
*/
ret = SET_ERROR(EIO);
zfs_ereport_post(FM_EREPORT_ZFS_AUTHENTICATION,
spa, NULL, zb, NULL, 0, 0);
}

return (ret);
}

/*
Expand Down Expand Up @@ -5810,7 +5823,8 @@ arc_read_done(zio_t *zio)
* Assert non-speculative zios didn't fail because an
* encryption key wasn't loaded
*/
ASSERT((zio->io_flags & ZIO_FLAG_SPECULATIVE) || error == 0);
ASSERT((zio->io_flags & ZIO_FLAG_SPECULATIVE) ||
error != ENOENT);

/*
* If we failed to decrypt, report an error now (as the zio
Expand Down Expand Up @@ -6033,12 +6047,24 @@ arc_read(zio_t *pio, spa_t *spa, const blkptr_t *bp,
rc = arc_buf_alloc_impl(hdr, spa, zb->zb_objset,
private, encrypted_read, compressed_read,
noauth_read, B_TRUE, &buf);
if (rc == ECKSUM) {
/*
* Convert authentication and decryption errors
* to EIO (and generate an ereport) before
* leaving the ARC.
*/
rc = SET_ERROR(EIO);
zfs_ereport_post(FM_EREPORT_ZFS_AUTHENTICATION,
spa, NULL, zb, NULL, 0, 0);
}
if (rc != 0) {
arc_buf_destroy(buf, private);
buf = NULL;
}

ASSERT((zio_flags & ZIO_FLAG_SPECULATIVE) || rc == 0);
/* assert any errors weren't due to unloaded keys */
ASSERT((zio_flags & ZIO_FLAG_SPECULATIVE) ||
rc != ENOENT);
} else if (*arc_flags & ARC_FLAG_PREFETCH &&
refcount_count(&hdr->b_l1hdr.b_refcnt) == 0) {
arc_hdr_set_flags(hdr, ARC_FLAG_PREFETCH);
Expand Down
20 changes: 14 additions & 6 deletions module/zfs/dbuf.c
Original file line number Diff line number Diff line change
Expand Up @@ -1194,8 +1194,10 @@ dbuf_read_impl(dmu_buf_impl_t *db, zio_t *zio, uint32_t flags)
DMU_OT_IS_ENCRYPTED(dn->dn_bonustype) &&
(flags & DB_RF_NO_DECRYPT) == 0 &&
arc_is_encrypted(dn_buf)) {
SET_BOOKMARK(&zb, dmu_objset_id(db->db_objset),
DMU_META_DNODE_OBJECT, 0, dn->dn_dbuf->db_blkid);
err = arc_untransform(dn_buf, dn->dn_objset->os_spa,
dmu_objset_id(dn->dn_objset), B_TRUE);
&zb, B_TRUE);
if (err != 0) {
DB_DNODE_EXIT(db);
mutex_exit(&db->db_mtx);
Expand Down Expand Up @@ -1264,8 +1266,7 @@ dbuf_read_impl(dmu_buf_impl_t *db, zio_t *zio, uint32_t flags)
if (DBUF_IS_L2CACHEABLE(db))
aflags |= ARC_FLAG_L2CACHE;

SET_BOOKMARK(&zb, db->db_objset->os_dsl_dataset ?
db->db_objset->os_dsl_dataset->ds_object : DMU_META_OBJSET,
SET_BOOKMARK(&zb, dmu_objset_id(db->db_objset),
db->db.db_object, db->db_level, db->db_blkid);

/*
Expand Down Expand Up @@ -1409,9 +1410,12 @@ dbuf_read(dmu_buf_impl_t *db, zio_t *zio, uint32_t flags)
if (db->db_buf != NULL && (flags & DB_RF_NO_DECRYPT) == 0 &&
(arc_is_encrypted(db->db_buf) ||
arc_get_compression(db->db_buf) != ZIO_COMPRESS_OFF)) {
zbookmark_phys_t zb;

SET_BOOKMARK(&zb, dmu_objset_id(db->db_objset),
db->db.db_object, db->db_level, db->db_blkid);
dbuf_fix_old_data(db, spa_syncing_txg(spa));
err = arc_untransform(db->db_buf, spa,
dmu_objset_id(db->db_objset), B_FALSE);
err = arc_untransform(db->db_buf, spa, &zb, B_FALSE);
dbuf_set_data(db, db->db_buf);
}
mutex_exit(&db->db_mtx);
Expand Down Expand Up @@ -3458,15 +3462,19 @@ dbuf_check_crypt(dbuf_dirty_record_t *dr)
ASSERT(MUTEX_HELD(&db->db_mtx));

if (!dr->dt.dl.dr_raw && arc_is_encrypted(db->db_buf)) {
zbookmark_phys_t zb;

/*
* Unfortunately, there is currently no mechanism for
* syncing context to handle decryption errors. An error
* here is only possible if an attacker maliciously
* changed a dnode block and updated the associated
* checksums going up the block tree.
*/
SET_BOOKMARK(&zb, dmu_objset_id(db->db_objset),
db->db.db_object, db->db_level, db->db_blkid);
err = arc_untransform(db->db_buf, db->db_objset->os_spa,
dmu_objset_id(db->db_objset), B_TRUE);
&zb, B_TRUE);
if (err)
panic("Invalid dnode block MAC");
} else if (dr->dt.dl.dr_raw) {
Expand Down
6 changes: 5 additions & 1 deletion module/zfs/dmu_objset.c
Original file line number Diff line number Diff line change
Expand Up @@ -686,8 +686,12 @@ dmu_objset_own_impl(dsl_dataset_t *ds, dmu_objset_type_t type,

/* if we are decrypting, we can now check MACs in os->os_phys_buf */
if (decrypt && arc_is_unauthenticated((*osp)->os_phys_buf)) {
zbookmark_phys_t zb;

SET_BOOKMARK(&zb, ds->ds_object, ZB_ROOT_OBJECT,
ZB_ROOT_LEVEL, ZB_ROOT_BLKID);
err = arc_untransform((*osp)->os_phys_buf, (*osp)->os_spa,
ds->ds_object, B_FALSE);
&zb, B_FALSE);
if (err != 0)
return (err);

Expand Down
6 changes: 5 additions & 1 deletion module/zfs/dmu_send.c
Original file line number Diff line number Diff line change
Expand Up @@ -983,8 +983,12 @@ dmu_send_impl(void *tag, dsl_pool_t *dp, dsl_dataset_t *to_ds,
*/
if (!rawok && os->os_encrypted &&
arc_is_unauthenticated(os->os_phys_buf)) {
zbookmark_phys_t zb;

SET_BOOKMARK(&zb, to_ds->ds_object, ZB_ROOT_OBJECT,
ZB_ROOT_LEVEL, ZB_ROOT_BLKID);
err = arc_untransform(os->os_phys_buf, os->os_spa,
to_ds->ds_object, B_FALSE);
&zb, B_FALSE);
if (err != 0) {
dsl_pool_rele(dp, tag);
return (err);
Expand Down
9 changes: 5 additions & 4 deletions module/zfs/zfs_fm.c
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ zfs_is_ratelimiting_event(const char *subclass, vdev_t *vd)

static void
zfs_ereport_start(nvlist_t **ereport_out, nvlist_t **detector_out,
const char *subclass, spa_t *spa, vdev_t *vd, zbookmark_phys_t *zb,
const char *subclass, spa_t *spa, vdev_t *vd, const zbookmark_phys_t *zb,
zio_t *zio, uint64_t stateoroffset, uint64_t size)
{
nvlist_t *ereport, *detector;
Expand Down Expand Up @@ -767,7 +767,8 @@ annotate_ecksum(nvlist_t *ereport, zio_bad_cksum_t *info,

void
zfs_ereport_post(const char *subclass, spa_t *spa, vdev_t *vd,
zbookmark_phys_t *zb, zio_t *zio, uint64_t stateoroffset, uint64_t size)
const zbookmark_phys_t *zb, zio_t *zio, uint64_t stateoroffset,
uint64_t size)
{
#ifdef _KERNEL
nvlist_t *ereport = NULL;
Expand All @@ -788,7 +789,7 @@ zfs_ereport_post(const char *subclass, spa_t *spa, vdev_t *vd,
}

void
zfs_ereport_start_checksum(spa_t *spa, vdev_t *vd, zbookmark_phys_t *zb,
zfs_ereport_start_checksum(spa_t *spa, vdev_t *vd, const zbookmark_phys_t *zb,
struct zio *zio, uint64_t offset, uint64_t length, void *arg,
zio_bad_cksum_t *info)
{
Expand Down Expand Up @@ -874,7 +875,7 @@ zfs_ereport_free_checksum(zio_cksum_report_t *rpt)


void
zfs_ereport_post_checksum(spa_t *spa, vdev_t *vd, zbookmark_phys_t *zb,
zfs_ereport_post_checksum(spa_t *spa, vdev_t *vd, const zbookmark_phys_t *zb,
struct zio *zio, uint64_t offset, uint64_t length,
const abd_t *good_data, const abd_t *bad_data, zio_bad_cksum_t *zbc)
{
Expand Down
2 changes: 1 addition & 1 deletion module/zfs/zio.c
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ zio_decrypt(zio_t *zio, abd_t *data, uint64_t size)
* the io_error. If this was not a speculative zio, create an ereport.
*/
if (ret == ECKSUM) {
ret = SET_ERROR(EIO);
zio->io_error = SET_ERROR(EIO);
if ((zio->io_flags & ZIO_FLAG_SPECULATIVE) == 0) {
zfs_ereport_post(FM_EREPORT_ZFS_AUTHENTICATION,
spa, NULL, &zio->io_bookmark, zio, 0, 0);
Expand Down

0 comments on commit a2c2ed1

Please sign in to comment.