diff options
Diffstat (limited to 'fs/xfs/libxfs/xfs_attr_leaf.c')
| -rw-r--r-- | fs/xfs/libxfs/xfs_attr_leaf.c | 148 | 
1 files changed, 122 insertions, 26 deletions
diff --git a/fs/xfs/libxfs/xfs_attr_leaf.c b/fs/xfs/libxfs/xfs_attr_leaf.c index 601eaa36f1ad..2135b8e67dcc 100644 --- a/fs/xfs/libxfs/xfs_attr_leaf.c +++ b/fs/xfs/libxfs/xfs_attr_leaf.c @@ -247,14 +247,15 @@ xfs_attr3_leaf_hdr_to_disk(  	}  } -static bool +static xfs_failaddr_t  xfs_attr3_leaf_verify( -	struct xfs_buf		*bp) +	struct xfs_buf			*bp)  { -	struct xfs_mount	*mp = bp->b_target->bt_mount; -	struct xfs_attr_leafblock *leaf = bp->b_addr; -	struct xfs_perag *pag = bp->b_pag; -	struct xfs_attr3_icleaf_hdr ichdr; +	struct xfs_attr3_icleaf_hdr	ichdr; +	struct xfs_mount		*mp = bp->b_target->bt_mount; +	struct xfs_attr_leafblock	*leaf = bp->b_addr; +	struct xfs_perag		*pag = bp->b_pag; +	struct xfs_attr_leaf_entry	*entries;  	xfs_attr3_leaf_hdr_from_disk(mp->m_attr_geo, &ichdr, leaf); @@ -262,17 +263,17 @@ xfs_attr3_leaf_verify(  		struct xfs_da3_node_hdr *hdr3 = bp->b_addr;  		if (ichdr.magic != XFS_ATTR3_LEAF_MAGIC) -			return false; +			return __this_address;  		if (!uuid_equal(&hdr3->info.uuid, &mp->m_sb.sb_meta_uuid)) -			return false; +			return __this_address;  		if (be64_to_cpu(hdr3->info.blkno) != bp->b_bn) -			return false; +			return __this_address;  		if (!xfs_log_check_lsn(mp, be64_to_cpu(hdr3->info.lsn))) -			return false; +			return __this_address;  	} else {  		if (ichdr.magic != XFS_ATTR_LEAF_MAGIC) -			return false; +			return __this_address;  	}  	/*  	 * In recovery there is a transient state where count == 0 is valid @@ -280,12 +281,27 @@ xfs_attr3_leaf_verify(  	 * if the attr didn't fit in shortform.  	 */  	if (pag && pag->pagf_init && ichdr.count == 0) -		return false; +		return __this_address; + +	/* +	 * firstused is the block offset of the first name info structure. +	 * Make sure it doesn't go off the block or crash into the header. +	 */ +	if (ichdr.firstused > mp->m_attr_geo->blksize) +		return __this_address; +	if (ichdr.firstused < xfs_attr3_leaf_hdr_size(leaf)) +		return __this_address; + +	/* Make sure the entries array doesn't crash into the name info. */ +	entries = xfs_attr3_leaf_entryp(bp->b_addr); +	if ((char *)&entries[ichdr.count] > +	    (char *)bp->b_addr + ichdr.firstused) +		return __this_address;  	/* XXX: need to range check rest of attr header values */  	/* XXX: hash order check? */ -	return true; +	return NULL;  }  static void @@ -293,12 +309,13 @@ xfs_attr3_leaf_write_verify(  	struct xfs_buf	*bp)  {  	struct xfs_mount	*mp = bp->b_target->bt_mount; -	struct xfs_buf_log_item	*bip = bp->b_fspriv; +	struct xfs_buf_log_item	*bip = bp->b_log_item;  	struct xfs_attr3_leaf_hdr *hdr3 = bp->b_addr; +	xfs_failaddr_t		fa; -	if (!xfs_attr3_leaf_verify(bp)) { -		xfs_buf_ioerror(bp, -EFSCORRUPTED); -		xfs_verifier_error(bp); +	fa = xfs_attr3_leaf_verify(bp); +	if (fa) { +		xfs_verifier_error(bp, -EFSCORRUPTED, fa);  		return;  	} @@ -322,21 +339,23 @@ xfs_attr3_leaf_read_verify(  	struct xfs_buf		*bp)  {  	struct xfs_mount	*mp = bp->b_target->bt_mount; +	xfs_failaddr_t		fa;  	if (xfs_sb_version_hascrc(&mp->m_sb) &&  	     !xfs_buf_verify_cksum(bp, XFS_ATTR3_LEAF_CRC_OFF)) -		xfs_buf_ioerror(bp, -EFSBADCRC); -	else if (!xfs_attr3_leaf_verify(bp)) -		xfs_buf_ioerror(bp, -EFSCORRUPTED); - -	if (bp->b_error) -		xfs_verifier_error(bp); +		xfs_verifier_error(bp, -EFSBADCRC, __this_address); +	else { +		fa = xfs_attr3_leaf_verify(bp); +		if (fa) +			xfs_verifier_error(bp, -EFSCORRUPTED, fa); +	}  }  const struct xfs_buf_ops xfs_attr3_leaf_buf_ops = {  	.name = "xfs_attr3_leaf",  	.verify_read = xfs_attr3_leaf_read_verify,  	.verify_write = xfs_attr3_leaf_write_verify, +	.verify_struct = xfs_attr3_leaf_verify,  };  int @@ -870,6 +889,80 @@ xfs_attr_shortform_allfit(  	return xfs_attr_shortform_bytesfit(dp, bytes);  } +/* Verify the consistency of an inline attribute fork. */ +xfs_failaddr_t +xfs_attr_shortform_verify( +	struct xfs_inode		*ip) +{ +	struct xfs_attr_shortform	*sfp; +	struct xfs_attr_sf_entry	*sfep; +	struct xfs_attr_sf_entry	*next_sfep; +	char				*endp; +	struct xfs_ifork		*ifp; +	int				i; +	int				size; + +	ASSERT(ip->i_d.di_aformat == XFS_DINODE_FMT_LOCAL); +	ifp = XFS_IFORK_PTR(ip, XFS_ATTR_FORK); +	sfp = (struct xfs_attr_shortform *)ifp->if_u1.if_data; +	size = ifp->if_bytes; + +	/* +	 * Give up if the attribute is way too short. +	 */ +	if (size < sizeof(struct xfs_attr_sf_hdr)) +		return __this_address; + +	endp = (char *)sfp + size; + +	/* Check all reported entries */ +	sfep = &sfp->list[0]; +	for (i = 0; i < sfp->hdr.count; i++) { +		/* +		 * struct xfs_attr_sf_entry has a variable length. +		 * Check the fixed-offset parts of the structure are +		 * within the data buffer. +		 */ +		if (((char *)sfep + sizeof(*sfep)) >= endp) +			return __this_address; + +		/* Don't allow names with known bad length. */ +		if (sfep->namelen == 0) +			return __this_address; + +		/* +		 * Check that the variable-length part of the structure is +		 * within the data buffer.  The next entry starts after the +		 * name component, so nextentry is an acceptable test. +		 */ +		next_sfep = XFS_ATTR_SF_NEXTENTRY(sfep); +		if ((char *)next_sfep > endp) +			return __this_address; + +		/* +		 * Check for unknown flags.  Short form doesn't support +		 * the incomplete or local bits, so we can use the namespace +		 * mask here. +		 */ +		if (sfep->flags & ~XFS_ATTR_NSP_ONDISK_MASK) +			return __this_address; + +		/* +		 * Check for invalid namespace combinations.  We only allow +		 * one namespace flag per xattr, so we can just count the +		 * bits (i.e. hweight) here. +		 */ +		if (hweight8(sfep->flags & XFS_ATTR_NSP_ONDISK_MASK) > 1) +			return __this_address; + +		sfep = next_sfep; +	} +	if ((void *)sfep != (void *)endp) +		return __this_address; + +	return NULL; +} +  /*   * Convert a leaf attribute list to shortform attribute list   */ @@ -2173,7 +2266,8 @@ xfs_attr3_leaf_lookup_int(  	leaf = bp->b_addr;  	xfs_attr3_leaf_hdr_from_disk(args->geo, &ichdr, leaf);  	entries = xfs_attr3_leaf_entryp(leaf); -	ASSERT(ichdr.count < args->geo->blksize / 8); +	if (ichdr.count >= args->geo->blksize / 8) +		return -EFSCORRUPTED;  	/*  	 * Binary search.  (note: small blocks will skip this loop) @@ -2189,8 +2283,10 @@ xfs_attr3_leaf_lookup_int(  		else  			break;  	} -	ASSERT(probe >= 0 && (!ichdr.count || probe < ichdr.count)); -	ASSERT(span <= 4 || be32_to_cpu(entry->hashval) == hashval); +	if (!(probe >= 0 && (!ichdr.count || probe < ichdr.count))) +		return -EFSCORRUPTED; +	if (!(span <= 4 || be32_to_cpu(entry->hashval) == hashval)) +		return -EFSCORRUPTED;  	/*  	 * Since we may have duplicate hashval's, find the first matching  |