diff options
author | James Pan <[email protected]> | 2013-12-06 14:16:40 -0800 |
---|---|---|
committer | Brian Behlendorf <[email protected]> | 2013-12-10 09:48:15 -0800 |
commit | 472e7c60853af099fbdf9d52162fd39818884f4f (patch) | |
tree | 1d41d2276874f7ff77758b43da48559f5985a46f /module/zfs/sa.c | |
parent | c1ab64d3931cbab45fbb197588cb27cb6fd10c33 (diff) |
sa_find_sizes() may compute wrong SA header size
Under the right conditions sa_find_sizes() will compute an incorrect
size of the system attribute (SA) header. This causes a failed assertion
when the SA_HDR_SIZE_MATCH_LAYOUT() test returns false, and may lead
to corruption of SA data.
The bug presents itself when there are more than two variable-length SAs
of just the right size to fit in the bonus buffer of a dnode. The
existing logic fails to account for the SA header space needed to store
the sizes of all the variable-length SAs.
A reproducer was possible on Linux by setting the xattr=sa dataset
property and storing xattrs on symbolic links (Issue #1648). Note the
corrupt link target name:
$ zfs set xattr=sa tank/fish
$ cd /tank/fish
$ ln -fs 12345678901234567 link
$ setfattr -n trusted.0000000000000000000 -v 0x000000000000000000000000 -h link
$ setfattr -n trusted.1111111111111111111 -v 0x000000000000000000000000 -h link
$ ls -l link
lrwxrwxrwx 1 root root 17 Dec 6 15:40 link -> 90123456701234567
Commit 6a7c0ccca44ad02c476a111d8f7911fc8b12fff7 worked around this bug
by forcing xattr's on symlinks to be stored in directory format. This
change implements a proper fix, so the workaround can now be reverted.
The reference link below contains a reproducer for FreeBSD.
References:
http://lists.open-zfs.org/pipermail/developer/2013-November/000306.html
Ported-by: Ned Bass <[email protected]>
Signed-off-by: Brian Behlendorf <[email protected]>
Closes #1890
Diffstat (limited to 'module/zfs/sa.c')
-rw-r--r-- | module/zfs/sa.c | 45 |
1 files changed, 21 insertions, 24 deletions
diff --git a/module/zfs/sa.c b/module/zfs/sa.c index 117d3868a..9dc6756dc 100644 --- a/module/zfs/sa.c +++ b/module/zfs/sa.c @@ -571,10 +571,9 @@ sa_find_sizes(sa_os_t *sa, sa_bulk_attr_t *attr_desc, int attr_count, { int var_size = 0; int i; - int j = -1; int full_space; int hdrsize; - boolean_t done = B_FALSE; + int extra_hdrsize; if (buftype == SA_BONUS && sa->sa_force_spill) { *total = 0; @@ -585,10 +584,9 @@ sa_find_sizes(sa_os_t *sa, sa_bulk_attr_t *attr_desc, int attr_count, *index = -1; *total = 0; + *will_spill = B_FALSE; - if (buftype == SA_BONUS) - *will_spill = B_FALSE; - + extra_hdrsize = 0; hdrsize = (SA_BONUSTYPE_FROM_DB(db) == DMU_OT_ZNODE) ? 0 : sizeof (sa_hdr_phys_t); @@ -600,8 +598,8 @@ sa_find_sizes(sa_os_t *sa, sa_bulk_attr_t *attr_desc, int attr_count, *total = P2ROUNDUP(*total, 8); *total += attr_desc[i].sa_length; - if (done) - goto next; + if (*will_spill) + continue; is_var_sz = (SA_REGISTERED_LEN(sa, attr_desc[i].sa_attr) == 0); if (is_var_sz) { @@ -609,21 +607,27 @@ sa_find_sizes(sa_os_t *sa, sa_bulk_attr_t *attr_desc, int attr_count, } if (is_var_sz && var_size > 1) { - if (P2ROUNDUP(hdrsize + sizeof (uint16_t), 8) + + /* Don't worry that the spill block might overflow. + * It will be resized if needed in sa_build_layouts(). + */ + if (buftype == SA_SPILL || + P2ROUNDUP(hdrsize + sizeof (uint16_t), 8) + *total < full_space) { /* * Account for header space used by array of * optional sizes of variable-length attributes. - * Record the index in case this increase needs - * to be reversed due to spill-over. + * Record the extra header size in case this + * increase needs to be reversed due to + * spill-over. */ hdrsize += sizeof (uint16_t); - j = i; + if (*index != -1) + extra_hdrsize += sizeof (uint16_t); } else { - done = B_TRUE; - *index = i; - if (buftype == SA_BONUS) - *will_spill = B_TRUE; + ASSERT(buftype == SA_BONUS); + if (*index == -1) + *index = i; + *will_spill = B_TRUE; continue; } } @@ -638,22 +642,15 @@ sa_find_sizes(sa_os_t *sa, sa_bulk_attr_t *attr_desc, int attr_count, (*total + P2ROUNDUP(hdrsize, 8)) > (full_space - sizeof (blkptr_t))) { *index = i; - done = B_TRUE; } -next: if ((*total + P2ROUNDUP(hdrsize, 8)) > full_space && buftype == SA_BONUS) *will_spill = B_TRUE; } - /* - * j holds the index of the last variable-sized attribute for - * which hdrsize was increased. Reverse the increase if that - * attribute will be relocated to the spill block. - */ - if (*will_spill && j == *index) - hdrsize -= sizeof (uint16_t); + if (*will_spill) + hdrsize -= extra_hdrsize; hdrsize = P2ROUNDUP(hdrsize, 8); return (hdrsize); |