Skip to content

Commit

Permalink
Prevent SA length overflow
Browse files Browse the repository at this point in the history
The function sa_update() accepts a 32-bit length parameter and
assigns it to a 16-bit field in sa_bulk_attr_t, potentially
truncating the passed-in value. This could lead to corrupt system
attribute (SA) records getting written to the pool. Add a VERIFY to
sa_update() to detect cases where overflow would occur. The SA length
is limited to 16-bit values by the on-disk format defined by
sa_hdr_phys_t.

The function zfs_sa_set_xattr() is vulnerable to this bug if the
unpacked nvlist of xattrs is less than 64k in size but the packed
size is greater than 64k. Fix this by appropriately checking the
size of the packed nvlist before calling sa_update(). Add error
handling to zpl_xattr_set_sa() to keep the cached list of SA-based
xattrs consistent with the data on disk.

Lastly, zfs_sa_set_xattr() calls dmu_tx_abort() on an assigned
transaction if sa_update() returns an error, but the DMU only allows
unassigned transactions to be aborted. Wrap the sa_update() call in a
VERIFY0, remove the transaction abort, and call dmu_tx_commit()
unconditionally. This is consistent practice with other callers
of sa_update().

Fixes openzfs#4150

Signed-off-by: Ned Bass <[email protected]>
  • Loading branch information
nedbass committed Dec 30, 2015
1 parent 23de906 commit 47dc2b1
Show file tree
Hide file tree
Showing 4 changed files with 16 additions and 7 deletions.
5 changes: 5 additions & 0 deletions include/sys/sa.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ typedef struct sa_bulk_attr {
b[idx++].sa_length = len; \
}

/*
* The on-disk format of sa_hdr_phys_t limits SA lengths to 16-bit values.
*/
#define SA_ATTR_MAX_LEN ((1 << (sizeof(uint16_t) * 8)) - 1)

typedef struct sa_os sa_os_t;

typedef enum sa_handle_type {
Expand Down
2 changes: 2 additions & 0 deletions module/zfs/sa.c
Original file line number Diff line number Diff line change
Expand Up @@ -1836,6 +1836,8 @@ sa_update(sa_handle_t *hdl, sa_attr_type_t type,
int error;
sa_bulk_attr_t bulk;

VERIFY3U(buflen, <=, SA_ATTR_MAX_LEN);

bulk.sa_attr = type;
bulk.sa_data_func = NULL;
bulk.sa_length = buflen;
Expand Down
11 changes: 5 additions & 6 deletions module/zfs/zfs_sa.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ zfs_sa_set_xattr(znode_t *zp)
error = nvlist_size(zp->z_xattr_cached, &size, NV_ENCODE_XDR);
if (error)
goto out;
if (size > SA_ATTR_MAX_LEN)
return (EFBIG);

obj = zio_buf_alloc(size);

Expand All @@ -247,12 +249,9 @@ zfs_sa_set_xattr(znode_t *zp)
if (error) {
dmu_tx_abort(tx);
} else {
error = sa_update(zp->z_sa_hdl, SA_ZPL_DXATTR(zsb),
obj, size, tx);
if (error)
dmu_tx_abort(tx);
else
dmu_tx_commit(tx);
VERIFY0(sa_update(zp->z_sa_hdl, SA_ZPL_DXATTR(zsb),
obj, size, tx));
dmu_tx_commit(tx);
}
out_free:
zio_buf_free(obj, size);
Expand Down
5 changes: 4 additions & 1 deletion module/zfs/zpl_xattr.c
Original file line number Diff line number Diff line change
Expand Up @@ -478,8 +478,11 @@ zpl_xattr_set_sa(struct inode *ip, const char *name, const void *value,
}

/* Update the SA for additions, modifications, and removals. */
if (!error)
if (!error) {
error = -zfs_sa_set_xattr(zp);
if (error && (value != NULL))
(void) nvlist_remove(nvl, name, DATA_TYPE_BYTE_ARRAY);
}

ASSERT3S(error, <=, 0);

Expand Down

0 comments on commit 47dc2b1

Please sign in to comment.