Re: [PATCH] xfs: fix intent use-after-free on abort

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Apr 03, 2018 at 11:44:58AM +1000, Dave Chinner wrote:
> From: Dave Chinner <dchinner@xxxxxxxxxx>
> 
> When an intent is aborted during it's initial commit through
> xfs_defer_trans_abort(), there is a use after free. The current
> report is for a RUI  through this path in generic/388:
> 
>  Freed by task 6274:
>   __kasan_slab_free+0x136/0x180
>   kmem_cache_free+0xe7/0x4b0
>   xfs_trans_free_items+0x198/0x2e0
>   __xfs_trans_commit+0x27f/0xcc0
>   xfs_trans_roll+0x17b/0x2a0
>   xfs_defer_trans_roll+0x6ad/0xe60
>   xfs_defer_finish+0x2a6/0x2140
>   xfs_alloc_file_space+0x53a/0xf90
>   xfs_file_fallocate+0x5c6/0xac0
>   vfs_fallocate+0x2f5/0x930
>   ioctl_preallocate+0x1dc/0x320
>   do_vfs_ioctl+0xfe4/0x1690
> 
> The problem is that the RUI has two active references - one in the
> current transaction, and another held by the defer_ops structure
> that is passed to the RUD (intent done) so that both the intent and
> the intent done structures are freed on commit of the intent done.
> 
> Hence during abort, we need to release the intent item, because the
> defer_ops reference is released separately via ->abort_intent
> callback. Fix all the intent code to do this correctly.
> 
> Signed-Off-By: Dave Chinner <dchinner@xxxxxxxxxx>
> ---
>  fs/xfs/xfs_bmap_item.c     | 39 ++++++++++++++++++++-------------------
>  fs/xfs/xfs_extfree_item.c  | 38 +++++++++++++++++++-------------------
>  fs/xfs/xfs_refcount_item.c | 39 ++++++++++++++++++++-------------------
>  fs/xfs/xfs_rmap_item.c     | 38 +++++++++++++++++++-------------------
>  4 files changed, 78 insertions(+), 76 deletions(-)
> 
> diff --git a/fs/xfs/xfs_bmap_item.c b/fs/xfs/xfs_bmap_item.c
> index d769a82f3641..618bb71535c8 100644
> --- a/fs/xfs/xfs_bmap_item.c
> +++ b/fs/xfs/xfs_bmap_item.c
> @@ -53,6 +53,25 @@ xfs_bui_item_free(
>  	kmem_zone_free(xfs_bui_zone, buip);
>  }
>  
> +/*
> + * Freeing the BUI requires that we remove it from the AIL if it has already
> + * been placed there. However, the BUI may not yet have been placed in the AIL
> + * when called by xfs_bui_release() from BUD processing due to the ordering of
> + * committed vs unpin operations in bulk insert operations. Hence the reference
> + * count to ensure only the last caller frees the BUI.
> + */
> +void
> +xfs_bui_release(
> +	struct xfs_bui_log_item	*buip)
> +{
> +	ASSERT(atomic_read(&buip->bui_refcount) > 0);
> +	if (atomic_dec_and_test(&buip->bui_refcount)) {
> +		xfs_trans_ail_remove(&buip->bui_item, SHUTDOWN_LOG_IO_ERROR);
> +		xfs_bui_item_free(buip);
> +	}
> +}
> +
> +
>  STATIC void
>  xfs_bui_item_size(
>  	struct xfs_log_item	*lip,
> @@ -142,7 +161,7 @@ xfs_bui_item_unlock(
>  	struct xfs_log_item	*lip)
>  {
>  	if (test_bit(XFS_LI_ABORTED, &lip->li_flags))

Hm, in my tree this is still "if (lip->li_flags & XFS_LI_ABORTED)" :)
/me wonders where the test_bit() came from?

But this is the same change that I came up with to solve the problem, so
I guess I'll go feed it to the test machines and see how they fare
overnight, and mash on it harder with generic/388 tomorrow.

--D

> -		xfs_bui_item_free(BUI_ITEM(lip));
> +		xfs_bui_release(BUI_ITEM(lip));
>  }
>  
>  /*
> @@ -206,24 +225,6 @@ xfs_bui_init(
>  	return buip;
>  }
>  
> -/*
> - * Freeing the BUI requires that we remove it from the AIL if it has already
> - * been placed there. However, the BUI may not yet have been placed in the AIL
> - * when called by xfs_bui_release() from BUD processing due to the ordering of
> - * committed vs unpin operations in bulk insert operations. Hence the reference
> - * count to ensure only the last caller frees the BUI.
> - */
> -void
> -xfs_bui_release(
> -	struct xfs_bui_log_item	*buip)
> -{
> -	ASSERT(atomic_read(&buip->bui_refcount) > 0);
> -	if (atomic_dec_and_test(&buip->bui_refcount)) {
> -		xfs_trans_ail_remove(&buip->bui_item, SHUTDOWN_LOG_IO_ERROR);
> -		xfs_bui_item_free(buip);
> -	}
> -}
> -
>  static inline struct xfs_bud_log_item *BUD_ITEM(struct xfs_log_item *lip)
>  {
>  	return container_of(lip, struct xfs_bud_log_item, bud_item);
> diff --git a/fs/xfs/xfs_extfree_item.c b/fs/xfs/xfs_extfree_item.c
> index d60a9809f0c6..70b7d48af6d6 100644
> --- a/fs/xfs/xfs_extfree_item.c
> +++ b/fs/xfs/xfs_extfree_item.c
> @@ -50,6 +50,24 @@ xfs_efi_item_free(
>  		kmem_zone_free(xfs_efi_zone, efip);
>  }
>  
> +/*
> + * Freeing the efi requires that we remove it from the AIL if it has already
> + * been placed there. However, the EFI may not yet have been placed in the AIL
> + * when called by xfs_efi_release() from EFD processing due to the ordering of
> + * committed vs unpin operations in bulk insert operations. Hence the reference
> + * count to ensure only the last caller frees the EFI.
> + */
> +void
> +xfs_efi_release(
> +	struct xfs_efi_log_item	*efip)
> +{
> +	ASSERT(atomic_read(&efip->efi_refcount) > 0);
> +	if (atomic_dec_and_test(&efip->efi_refcount)) {
> +		xfs_trans_ail_remove(&efip->efi_item, SHUTDOWN_LOG_IO_ERROR);
> +		xfs_efi_item_free(efip);
> +	}
> +}
> +
>  /*
>   * This returns the number of iovecs needed to log the given efi item.
>   * We only need 1 iovec for an efi item.  It just logs the efi_log_format
> @@ -151,7 +169,7 @@ xfs_efi_item_unlock(
>  	struct xfs_log_item	*lip)
>  {
>  	if (test_bit(XFS_LI_ABORTED, &lip->li_flags))
> -		xfs_efi_item_free(EFI_ITEM(lip));
> +		xfs_efi_release(EFI_ITEM(lip));
>  }
>  
>  /*
> @@ -279,24 +297,6 @@ xfs_efi_copy_format(xfs_log_iovec_t *buf, xfs_efi_log_format_t *dst_efi_fmt)
>  	return -EFSCORRUPTED;
>  }
>  
> -/*
> - * Freeing the efi requires that we remove it from the AIL if it has already
> - * been placed there. However, the EFI may not yet have been placed in the AIL
> - * when called by xfs_efi_release() from EFD processing due to the ordering of
> - * committed vs unpin operations in bulk insert operations. Hence the reference
> - * count to ensure only the last caller frees the EFI.
> - */
> -void
> -xfs_efi_release(
> -	struct xfs_efi_log_item	*efip)
> -{
> -	ASSERT(atomic_read(&efip->efi_refcount) > 0);
> -	if (atomic_dec_and_test(&efip->efi_refcount)) {
> -		xfs_trans_ail_remove(&efip->efi_item, SHUTDOWN_LOG_IO_ERROR);
> -		xfs_efi_item_free(efip);
> -	}
> -}
> -
>  static inline struct xfs_efd_log_item *EFD_ITEM(struct xfs_log_item *lip)
>  {
>  	return container_of(lip, struct xfs_efd_log_item, efd_item);
> diff --git a/fs/xfs/xfs_refcount_item.c b/fs/xfs/xfs_refcount_item.c
> index a017024bf249..e5866b714d5f 100644
> --- a/fs/xfs/xfs_refcount_item.c
> +++ b/fs/xfs/xfs_refcount_item.c
> @@ -52,6 +52,25 @@ xfs_cui_item_free(
>  		kmem_zone_free(xfs_cui_zone, cuip);
>  }
>  
> +/*
> + * Freeing the CUI requires that we remove it from the AIL if it has already
> + * been placed there. However, the CUI may not yet have been placed in the AIL
> + * when called by xfs_cui_release() from CUD processing due to the ordering of
> + * committed vs unpin operations in bulk insert operations. Hence the reference
> + * count to ensure only the last caller frees the CUI.
> + */
> +void
> +xfs_cui_release(
> +	struct xfs_cui_log_item	*cuip)
> +{
> +	ASSERT(atomic_read(&cuip->cui_refcount) > 0);
> +	if (atomic_dec_and_test(&cuip->cui_refcount)) {
> +		xfs_trans_ail_remove(&cuip->cui_item, SHUTDOWN_LOG_IO_ERROR);
> +		xfs_cui_item_free(cuip);
> +	}
> +}
> +
> +
>  STATIC void
>  xfs_cui_item_size(
>  	struct xfs_log_item	*lip,
> @@ -141,7 +160,7 @@ xfs_cui_item_unlock(
>  	struct xfs_log_item	*lip)
>  {
>  	if (test_bit(XFS_LI_ABORTED, &lip->li_flags))
> -		xfs_cui_item_free(CUI_ITEM(lip));
> +		xfs_cui_release(CUI_ITEM(lip));
>  }
>  
>  /*
> @@ -211,24 +230,6 @@ xfs_cui_init(
>  	return cuip;
>  }
>  
> -/*
> - * Freeing the CUI requires that we remove it from the AIL if it has already
> - * been placed there. However, the CUI may not yet have been placed in the AIL
> - * when called by xfs_cui_release() from CUD processing due to the ordering of
> - * committed vs unpin operations in bulk insert operations. Hence the reference
> - * count to ensure only the last caller frees the CUI.
> - */
> -void
> -xfs_cui_release(
> -	struct xfs_cui_log_item	*cuip)
> -{
> -	ASSERT(atomic_read(&cuip->cui_refcount) > 0);
> -	if (atomic_dec_and_test(&cuip->cui_refcount)) {
> -		xfs_trans_ail_remove(&cuip->cui_item, SHUTDOWN_LOG_IO_ERROR);
> -		xfs_cui_item_free(cuip);
> -	}
> -}
> -
>  static inline struct xfs_cud_log_item *CUD_ITEM(struct xfs_log_item *lip)
>  {
>  	return container_of(lip, struct xfs_cud_log_item, cud_item);
> diff --git a/fs/xfs/xfs_rmap_item.c b/fs/xfs/xfs_rmap_item.c
> index cbf4ecd81616..e5b5b3e7ef82 100644
> --- a/fs/xfs/xfs_rmap_item.c
> +++ b/fs/xfs/xfs_rmap_item.c
> @@ -52,6 +52,24 @@ xfs_rui_item_free(
>  		kmem_zone_free(xfs_rui_zone, ruip);
>  }
>  
> +/*
> + * Freeing the RUI requires that we remove it from the AIL if it has already
> + * been placed there. However, the RUI may not yet have been placed in the AIL
> + * when called by xfs_rui_release() from RUD processing due to the ordering of
> + * committed vs unpin operations in bulk insert operations. Hence the reference
> + * count to ensure only the last caller frees the RUI.
> + */
> +void
> +xfs_rui_release(
> +	struct xfs_rui_log_item	*ruip)
> +{
> +	ASSERT(atomic_read(&ruip->rui_refcount) > 0);
> +	if (atomic_dec_and_test(&ruip->rui_refcount)) {
> +		xfs_trans_ail_remove(&ruip->rui_item, SHUTDOWN_LOG_IO_ERROR);
> +		xfs_rui_item_free(ruip);
> +	}
> +}
> +
>  STATIC void
>  xfs_rui_item_size(
>  	struct xfs_log_item	*lip,
> @@ -141,7 +159,7 @@ xfs_rui_item_unlock(
>  	struct xfs_log_item	*lip)
>  {
>  	if (test_bit(XFS_LI_ABORTED, &lip->li_flags))
> -		xfs_rui_item_free(RUI_ITEM(lip));
> +		xfs_rui_release(RUI_ITEM(lip));
>  }
>  
>  /*
> @@ -233,24 +251,6 @@ xfs_rui_copy_format(
>  	return 0;
>  }
>  
> -/*
> - * Freeing the RUI requires that we remove it from the AIL if it has already
> - * been placed there. However, the RUI may not yet have been placed in the AIL
> - * when called by xfs_rui_release() from RUD processing due to the ordering of
> - * committed vs unpin operations in bulk insert operations. Hence the reference
> - * count to ensure only the last caller frees the RUI.
> - */
> -void
> -xfs_rui_release(
> -	struct xfs_rui_log_item	*ruip)
> -{
> -	ASSERT(atomic_read(&ruip->rui_refcount) > 0);
> -	if (atomic_dec_and_test(&ruip->rui_refcount)) {
> -		xfs_trans_ail_remove(&ruip->rui_item, SHUTDOWN_LOG_IO_ERROR);
> -		xfs_rui_item_free(ruip);
> -	}
> -}
> -
>  static inline struct xfs_rud_log_item *RUD_ITEM(struct xfs_log_item *lip)
>  {
>  	return container_of(lip, struct xfs_rud_log_item, rud_item);
> -- 
> 2.16.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-xfs" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [XFS Filesystem Development (older mail)]     [Linux Filesystem Development]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux RAID]     [Linux SCSI]


  Powered by Linux