diff mbox series

[v2] cifs: Fix leak when handling lease break for cached root fid

Message ID 20200630023003.1858066-1-paul@darkrain42.org (mailing list archive)
State New, archived
Headers show
Series [v2] cifs: Fix leak when handling lease break for cached root fid | expand

Commit Message

Paul Aurich June 30, 2020, 2:30 a.m. UTC
Handling a lease break for the cached root didn't free the
smb2_lease_break_work allocation, resulting in a leak:

    unreferenced object 0xffff98383a5af480 (size 128):
      comm "cifsd", pid 684, jiffies 4294936606 (age 534.868s)
      hex dump (first 32 bytes):
        c0 ff ff ff 1f 00 00 00 88 f4 5a 3a 38 98 ff ff  ..........Z:8...
        88 f4 5a 3a 38 98 ff ff 80 88 d6 8a ff ff ff ff  ..Z:8...........
      backtrace:
        [<0000000068957336>] smb2_is_valid_oplock_break+0x1fa/0x8c0
        [<0000000073b70b9e>] cifs_demultiplex_thread+0x73d/0xcc0
        [<00000000905fa372>] kthread+0x11c/0x150
        [<0000000079378e4e>] ret_from_fork+0x22/0x30

Avoid this leak by only allocating when necessary.

Fixes: a93864d93977 ("cifs: add lease tracking to the cached root fid")
Signed-off-by: Paul Aurich <paul@darkrain42.org>
CC: Stable <stable@vger.kernel.org> # v4.18+
---
 fs/cifs/smb2misc.c | 47 ++++++++++++++++++++++++++--------------------
 1 file changed, 27 insertions(+), 20 deletions(-)

Comments

Aurélien Aptel July 1, 2020, 1:13 p.m. UTC | #1
Great, thanks.

Reviewed-by: Aurelien Aptel <aaptel@suse.com>

Cheers,
Steve French July 2, 2020, 12:53 a.m. UTC | #2
I get the following sparse warning compiling this patch (make C=1 -C
/usr/src/linux-headers-`uname -r` M=`pwd` modules
CF=-D__CHECK_ENDIAN__)

  CHECK   /home/smfrench/cifs-2.6/fs/cifs/smb2misc.c
/home/smfrench/cifs-2.6/fs/cifs/smb2misc.c:542:24: warning: context
imbalance in 'smb2_tcon_has_lease' - unexpected unlock
/home/smfrench/cifs-2.6/fs/cifs/smb2misc.c:594:1: warning: context
imbalance in 'smb2_is_valid_lease_break' - wrong count at exit
  CC [M]  /home/smfrench/cifs-2.6/fs/cifs/smb2misc.o

On Mon, Jun 29, 2020 at 9:30 PM Paul Aurich <paul@darkrain42.org> wrote:
>
> Handling a lease break for the cached root didn't free the
> smb2_lease_break_work allocation, resulting in a leak:
>
>     unreferenced object 0xffff98383a5af480 (size 128):
>       comm "cifsd", pid 684, jiffies 4294936606 (age 534.868s)
>       hex dump (first 32 bytes):
>         c0 ff ff ff 1f 00 00 00 88 f4 5a 3a 38 98 ff ff  ..........Z:8...
>         88 f4 5a 3a 38 98 ff ff 80 88 d6 8a ff ff ff ff  ..Z:8...........
>       backtrace:
>         [<0000000068957336>] smb2_is_valid_oplock_break+0x1fa/0x8c0
>         [<0000000073b70b9e>] cifs_demultiplex_thread+0x73d/0xcc0
>         [<00000000905fa372>] kthread+0x11c/0x150
>         [<0000000079378e4e>] ret_from_fork+0x22/0x30
>
> Avoid this leak by only allocating when necessary.
>
> Fixes: a93864d93977 ("cifs: add lease tracking to the cached root fid")
> Signed-off-by: Paul Aurich <paul@darkrain42.org>
> CC: Stable <stable@vger.kernel.org> # v4.18+
> ---
>  fs/cifs/smb2misc.c | 47 ++++++++++++++++++++++++++--------------------
>  1 file changed, 27 insertions(+), 20 deletions(-)
>
> diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c
> index 6a39451973f8..570c0521fc3c 100644
> --- a/fs/cifs/smb2misc.c
> +++ b/fs/cifs/smb2misc.c
> @@ -505,8 +505,7 @@ cifs_ses_oplock_break(struct work_struct *work)
>  }
>
>  static bool
> -smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
> -                   struct smb2_lease_break_work *lw)
> +smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp)
>  {
>         bool found;
>         __u8 lease_state;
> @@ -516,9 +515,13 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
>         struct cifsInodeInfo *cinode;
>         int ack_req = le32_to_cpu(rsp->Flags &
>                                   SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED);
> +       struct smb2_lease_break_work *lw;
> +       struct tcon_link *tlink;
> +       __u8 lease_key[SMB2_LEASE_KEY_SIZE];
>
>         lease_state = le32_to_cpu(rsp->NewLeaseState);
>
> +       spin_lock(&tcon->open_file_lock);
>         list_for_each(tmp, &tcon->openFileList) {
>                 cfile = list_entry(tmp, struct cifsFileInfo, tlist);
>                 cinode = CIFS_I(d_inode(cfile->dentry));
> @@ -542,7 +545,8 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
>                 cfile->oplock_level = lease_state;
>
>                 cifs_queue_oplock_break(cfile);
> -               kfree(lw);
> +               spin_unlock(&tcon->open_file_lock);
> +               spin_unlock(&cifs_tcp_ses_lock);
>                 return true;
>         }
>
> @@ -554,10 +558,9 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
>
>                 if (!found && ack_req) {
>                         found = true;
> -                       memcpy(lw->lease_key, open->lease_key,
> +                       memcpy(lease_key, open->lease_key,
>                                SMB2_LEASE_KEY_SIZE);
> -                       lw->tlink = cifs_get_tlink(open->tlink);
> -                       queue_work(cifsiod_wq, &lw->lease_break);
> +                       tlink = cifs_get_tlink(open->tlink);
>                 }
>
>                 cifs_dbg(FYI, "found in the pending open list\n");
> @@ -567,6 +570,23 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
>                 open->oplock = lease_state;
>         }
>
> +       spin_unlock(&tcon->open_file_lock);
> +       if (found) {
> +               spin_unlock(&cifs_tcp_ses_lock);
> +
> +               lw = kmalloc(sizeof(struct smb2_lease_break_work), GFP_KERNEL);
> +               if (!lw) {
> +                       cifs_put_tlink(tlink);
> +                       return true;
> +               }
> +
> +               INIT_WORK(&lw->lease_break, cifs_ses_oplock_break);
> +               lw->tlink = tlink;
> +               lw->lease_state = rsp->NewLeaseState;
> +               memcpy(lw->lease_key, lease_key, SMB2_LEASE_KEY_SIZE);
> +               queue_work(cifsiod_wq, &lw->lease_break);
> +       }
> +
>         return found;
>  }
>
> @@ -578,14 +598,6 @@ smb2_is_valid_lease_break(char *buffer)
>         struct TCP_Server_Info *server;
>         struct cifs_ses *ses;
>         struct cifs_tcon *tcon;
> -       struct smb2_lease_break_work *lw;
> -
> -       lw = kmalloc(sizeof(struct smb2_lease_break_work), GFP_KERNEL);
> -       if (!lw)
> -               return false;
> -
> -       INIT_WORK(&lw->lease_break, cifs_ses_oplock_break);
> -       lw->lease_state = rsp->NewLeaseState;
>
>         cifs_dbg(FYI, "Checking for lease break\n");
>
> @@ -600,15 +612,11 @@ smb2_is_valid_lease_break(char *buffer)
>                         list_for_each(tmp2, &ses->tcon_list) {
>                                 tcon = list_entry(tmp2, struct cifs_tcon,
>                                                   tcon_list);
> -                               spin_lock(&tcon->open_file_lock);
>                                 cifs_stats_inc(
>                                     &tcon->stats.cifs_stats.num_oplock_brks);
> -                               if (smb2_tcon_has_lease(tcon, rsp, lw)) {
> -                                       spin_unlock(&tcon->open_file_lock);
> -                                       spin_unlock(&cifs_tcp_ses_lock);
> +                               if (smb2_tcon_has_lease(tcon, rsp)) {
>                                         return true;
>                                 }
> -                               spin_unlock(&tcon->open_file_lock);
>
>                                 if (tcon->crfid.is_valid &&
>                                     !memcmp(rsp->LeaseKey,
> @@ -625,7 +633,6 @@ smb2_is_valid_lease_break(char *buffer)
>                 }
>         }
>         spin_unlock(&cifs_tcp_ses_lock);
> -       kfree(lw);
>         cifs_dbg(FYI, "Can not process lease break - no lease matched\n");
>         return false;
>  }
> --
> 2.27.0
>
diff mbox series

Patch

diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c
index 6a39451973f8..570c0521fc3c 100644
--- a/fs/cifs/smb2misc.c
+++ b/fs/cifs/smb2misc.c
@@ -505,8 +505,7 @@  cifs_ses_oplock_break(struct work_struct *work)
 }
 
 static bool
-smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
-		    struct smb2_lease_break_work *lw)
+smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp)
 {
 	bool found;
 	__u8 lease_state;
@@ -516,9 +515,13 @@  smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
 	struct cifsInodeInfo *cinode;
 	int ack_req = le32_to_cpu(rsp->Flags &
 				  SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED);
+	struct smb2_lease_break_work *lw;
+	struct tcon_link *tlink;
+	__u8 lease_key[SMB2_LEASE_KEY_SIZE];
 
 	lease_state = le32_to_cpu(rsp->NewLeaseState);
 
+	spin_lock(&tcon->open_file_lock);
 	list_for_each(tmp, &tcon->openFileList) {
 		cfile = list_entry(tmp, struct cifsFileInfo, tlist);
 		cinode = CIFS_I(d_inode(cfile->dentry));
@@ -542,7 +545,8 @@  smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
 		cfile->oplock_level = lease_state;
 
 		cifs_queue_oplock_break(cfile);
-		kfree(lw);
+		spin_unlock(&tcon->open_file_lock);
+		spin_unlock(&cifs_tcp_ses_lock);
 		return true;
 	}
 
@@ -554,10 +558,9 @@  smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
 
 		if (!found && ack_req) {
 			found = true;
-			memcpy(lw->lease_key, open->lease_key,
+			memcpy(lease_key, open->lease_key,
 			       SMB2_LEASE_KEY_SIZE);
-			lw->tlink = cifs_get_tlink(open->tlink);
-			queue_work(cifsiod_wq, &lw->lease_break);
+			tlink = cifs_get_tlink(open->tlink);
 		}
 
 		cifs_dbg(FYI, "found in the pending open list\n");
@@ -567,6 +570,23 @@  smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
 		open->oplock = lease_state;
 	}
 
+	spin_unlock(&tcon->open_file_lock);
+	if (found) {
+		spin_unlock(&cifs_tcp_ses_lock);
+
+		lw = kmalloc(sizeof(struct smb2_lease_break_work), GFP_KERNEL);
+		if (!lw) {
+			cifs_put_tlink(tlink);
+			return true;
+		}
+
+		INIT_WORK(&lw->lease_break, cifs_ses_oplock_break);
+		lw->tlink = tlink;
+		lw->lease_state = rsp->NewLeaseState;
+		memcpy(lw->lease_key, lease_key, SMB2_LEASE_KEY_SIZE);
+		queue_work(cifsiod_wq, &lw->lease_break);
+	}
+
 	return found;
 }
 
@@ -578,14 +598,6 @@  smb2_is_valid_lease_break(char *buffer)
 	struct TCP_Server_Info *server;
 	struct cifs_ses *ses;
 	struct cifs_tcon *tcon;
-	struct smb2_lease_break_work *lw;
-
-	lw = kmalloc(sizeof(struct smb2_lease_break_work), GFP_KERNEL);
-	if (!lw)
-		return false;
-
-	INIT_WORK(&lw->lease_break, cifs_ses_oplock_break);
-	lw->lease_state = rsp->NewLeaseState;
 
 	cifs_dbg(FYI, "Checking for lease break\n");
 
@@ -600,15 +612,11 @@  smb2_is_valid_lease_break(char *buffer)
 			list_for_each(tmp2, &ses->tcon_list) {
 				tcon = list_entry(tmp2, struct cifs_tcon,
 						  tcon_list);
-				spin_lock(&tcon->open_file_lock);
 				cifs_stats_inc(
 				    &tcon->stats.cifs_stats.num_oplock_brks);
-				if (smb2_tcon_has_lease(tcon, rsp, lw)) {
-					spin_unlock(&tcon->open_file_lock);
-					spin_unlock(&cifs_tcp_ses_lock);
+				if (smb2_tcon_has_lease(tcon, rsp)) {
 					return true;
 				}
-				spin_unlock(&tcon->open_file_lock);
 
 				if (tcon->crfid.is_valid &&
 				    !memcmp(rsp->LeaseKey,
@@ -625,7 +633,6 @@  smb2_is_valid_lease_break(char *buffer)
 		}
 	}
 	spin_unlock(&cifs_tcp_ses_lock);
-	kfree(lw);
 	cifs_dbg(FYI, "Can not process lease break - no lease matched\n");
 	return false;
 }