diff mbox

tcmu: fix crash during device removal

Message ID 1495007957-5770-1-git-send-email-mchristi@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Mike Christie May 17, 2017, 7:59 a.m. UTC
We currently do

tcmu_free_device ->tcmu_netlink_event(TCMU_CMD_REMOVED_DEVICE) ->
kfree(tcmu_dev) via rcu call.

The problem is that the kernel does not wait for userspace to
do close() on the uio device before freeing the tcmu_dev.
We can then hit a race where the kernel frees the tcmu_dev before
userspace does close() and so when close() -> release -> tcmu_release
is done, we try to access a freed tcmu_dev.

This patch made over the target-pending master branch moves the freeing of
the tcmu_dev to tcmu_release.

Signed-off-by: Mike Christie <mchristi@redhat.com>

---
 drivers/target/target_core_user.c | 25 ++++++++++++++-----------
 1 file changed, 14 insertions(+), 11 deletions(-)

Comments

Mike Christie May 17, 2017, 8:11 a.m. UTC | #1
Nick,

Do not merge this patch. It will fix the problem, but cause a regression
if there was a failure during setup.

On 05/17/2017 02:59 AM, Mike Christie wrote:
> We currently do
> 
> tcmu_free_device ->tcmu_netlink_event(TCMU_CMD_REMOVED_DEVICE) ->
> kfree(tcmu_dev) via rcu call.
> 
> The problem is that the kernel does not wait for userspace to
> do close() on the uio device before freeing the tcmu_dev.
> We can then hit a race where the kernel frees the tcmu_dev before
> userspace does close() and so when close() -> release -> tcmu_release
> is done, we try to access a freed tcmu_dev.
> 
> This patch made over the target-pending master branch moves the freeing of
> the tcmu_dev to tcmu_release.
> 
> Signed-off-by: Mike Christie <mchristi@redhat.com>
> 
> ---
>  drivers/target/target_core_user.c | 25 ++++++++++++++-----------
>  1 file changed, 14 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/target/target_core_user.c b/drivers/target/target_core_user.c
> index 9045837..cda0bd7 100644
> --- a/drivers/target/target_core_user.c
> +++ b/drivers/target/target_core_user.c
> @@ -1145,14 +1145,26 @@ static int tcmu_open(struct uio_info *info, struct inode *inode)
>  	return 0;
>  }
>  
> +static void tcmu_dev_call_rcu(struct rcu_head *p)
> +{
> +	struct se_device *dev = container_of(p, struct se_device, rcu_head);
> +	struct tcmu_dev *udev = TCMU_DEV(dev);
> +
> +	kfree(udev);
> +}
> +
>  static int tcmu_release(struct uio_info *info, struct inode *inode)
>  {
>  	struct tcmu_dev *udev = container_of(info, struct tcmu_dev, uio_info);
> +	struct se_device *dev = &udev->se_dev;
>  
>  	clear_bit(TCMU_DEV_BIT_OPEN, &udev->flags);
>  
>  	pr_debug("close\n");
>  
> +	kfree(udev->uio_info.name);
> +	kfree(udev->name);
> +	call_rcu(&dev->rcu_head, tcmu_dev_call_rcu);
>  	return 0;
>  }
>  
> @@ -1302,14 +1314,6 @@ static int tcmu_check_and_free_pending_cmd(struct tcmu_cmd *cmd)
>  	return -EINVAL;
>  }
>  
> -static void tcmu_dev_call_rcu(struct rcu_head *p)
> -{
> -	struct se_device *dev = container_of(p, struct se_device, rcu_head);
> -	struct tcmu_dev *udev = TCMU_DEV(dev);
> -
> -	kfree(udev);
> -}
> -
>  static bool tcmu_dev_configured(struct tcmu_dev *udev)
>  {
>  	return udev->uio_info.uio_dev ? true : false;
> @@ -1364,10 +1368,9 @@ static void tcmu_free_device(struct se_device *dev)
>  				   udev->uio_info.uio_dev->minor);
>  
>  		uio_unregister_device(&udev->uio_info);
> -		kfree(udev->uio_info.name);
> -		kfree(udev->name);
> +	} else {
> +		call_rcu(&dev->rcu_head, tcmu_dev_call_rcu);
>  	}
> -	call_rcu(&dev->rcu_head, tcmu_dev_call_rcu);
>  }
>  
>  enum {
> 

--
To unsubscribe from this list: send the line "unsubscribe target-devel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/target/target_core_user.c b/drivers/target/target_core_user.c
index 9045837..cda0bd7 100644
--- a/drivers/target/target_core_user.c
+++ b/drivers/target/target_core_user.c
@@ -1145,14 +1145,26 @@  static int tcmu_open(struct uio_info *info, struct inode *inode)
 	return 0;
 }
 
+static void tcmu_dev_call_rcu(struct rcu_head *p)
+{
+	struct se_device *dev = container_of(p, struct se_device, rcu_head);
+	struct tcmu_dev *udev = TCMU_DEV(dev);
+
+	kfree(udev);
+}
+
 static int tcmu_release(struct uio_info *info, struct inode *inode)
 {
 	struct tcmu_dev *udev = container_of(info, struct tcmu_dev, uio_info);
+	struct se_device *dev = &udev->se_dev;
 
 	clear_bit(TCMU_DEV_BIT_OPEN, &udev->flags);
 
 	pr_debug("close\n");
 
+	kfree(udev->uio_info.name);
+	kfree(udev->name);
+	call_rcu(&dev->rcu_head, tcmu_dev_call_rcu);
 	return 0;
 }
 
@@ -1302,14 +1314,6 @@  static int tcmu_check_and_free_pending_cmd(struct tcmu_cmd *cmd)
 	return -EINVAL;
 }
 
-static void tcmu_dev_call_rcu(struct rcu_head *p)
-{
-	struct se_device *dev = container_of(p, struct se_device, rcu_head);
-	struct tcmu_dev *udev = TCMU_DEV(dev);
-
-	kfree(udev);
-}
-
 static bool tcmu_dev_configured(struct tcmu_dev *udev)
 {
 	return udev->uio_info.uio_dev ? true : false;
@@ -1364,10 +1368,9 @@  static void tcmu_free_device(struct se_device *dev)
 				   udev->uio_info.uio_dev->minor);
 
 		uio_unregister_device(&udev->uio_info);
-		kfree(udev->uio_info.name);
-		kfree(udev->name);
+	} else {
+		call_rcu(&dev->rcu_head, tcmu_dev_call_rcu);
 	}
-	call_rcu(&dev->rcu_head, tcmu_dev_call_rcu);
 }
 
 enum {