Commit d9710cc6 authored by Paul Aurich's avatar Paul Aurich Committed by Greg Kroah-Hartman

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

commit baf57b56 upstream.

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: a93864d9 ("cifs: add lease tracking to the cached root fid")
Signed-off-by: default avatarPaul Aurich <paul@darkrain42.org>
CC: Stable <stable@vger.kernel.org> # v4.18+
Reviewed-by: default avatarAurelien Aptel <aaptel@suse.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 6ffc89ca
...@@ -509,15 +509,31 @@ cifs_ses_oplock_break(struct work_struct *work) ...@@ -509,15 +509,31 @@ cifs_ses_oplock_break(struct work_struct *work)
kfree(lw); kfree(lw);
} }
static void
smb2_queue_pending_open_break(struct tcon_link *tlink, __u8 *lease_key,
__le32 new_lease_state)
{
struct smb2_lease_break_work *lw;
lw = kmalloc(sizeof(struct smb2_lease_break_work), GFP_KERNEL);
if (!lw) {
cifs_put_tlink(tlink);
return;
}
INIT_WORK(&lw->lease_break, cifs_ses_oplock_break);
lw->tlink = tlink;
lw->lease_state = new_lease_state;
memcpy(lw->lease_key, lease_key, SMB2_LEASE_KEY_SIZE);
queue_work(cifsiod_wq, &lw->lease_break);
}
static bool static bool
smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp, smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp)
struct smb2_lease_break_work *lw)
{ {
bool found;
__u8 lease_state; __u8 lease_state;
struct list_head *tmp; struct list_head *tmp;
struct cifsFileInfo *cfile; struct cifsFileInfo *cfile;
struct cifs_pending_open *open;
struct cifsInodeInfo *cinode; struct cifsInodeInfo *cinode;
int ack_req = le32_to_cpu(rsp->Flags & int ack_req = le32_to_cpu(rsp->Flags &
SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED); SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED);
...@@ -556,22 +572,29 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp, ...@@ -556,22 +572,29 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
&cinode->flags); &cinode->flags);
cifs_queue_oplock_break(cfile); cifs_queue_oplock_break(cfile);
kfree(lw);
return true; return true;
} }
found = false; return false;
}
static struct cifs_pending_open *
smb2_tcon_find_pending_open_lease(struct cifs_tcon *tcon,
struct smb2_lease_break *rsp)
{
__u8 lease_state = le32_to_cpu(rsp->NewLeaseState);
int ack_req = le32_to_cpu(rsp->Flags &
SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED);
struct cifs_pending_open *open;
struct cifs_pending_open *found = NULL;
list_for_each_entry(open, &tcon->pending_opens, olist) { list_for_each_entry(open, &tcon->pending_opens, olist) {
if (memcmp(open->lease_key, rsp->LeaseKey, if (memcmp(open->lease_key, rsp->LeaseKey,
SMB2_LEASE_KEY_SIZE)) SMB2_LEASE_KEY_SIZE))
continue; continue;
if (!found && ack_req) { if (!found && ack_req) {
found = true; found = open;
memcpy(lw->lease_key, open->lease_key,
SMB2_LEASE_KEY_SIZE);
lw->tlink = cifs_get_tlink(open->tlink);
queue_work(cifsiod_wq, &lw->lease_break);
} }
cifs_dbg(FYI, "found in the pending open list\n"); cifs_dbg(FYI, "found in the pending open list\n");
...@@ -592,14 +615,7 @@ smb2_is_valid_lease_break(char *buffer) ...@@ -592,14 +615,7 @@ smb2_is_valid_lease_break(char *buffer)
struct TCP_Server_Info *server; struct TCP_Server_Info *server;
struct cifs_ses *ses; struct cifs_ses *ses;
struct cifs_tcon *tcon; struct cifs_tcon *tcon;
struct smb2_lease_break_work *lw; struct cifs_pending_open *open;
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"); cifs_dbg(FYI, "Checking for lease break\n");
...@@ -617,11 +633,27 @@ smb2_is_valid_lease_break(char *buffer) ...@@ -617,11 +633,27 @@ smb2_is_valid_lease_break(char *buffer)
spin_lock(&tcon->open_file_lock); spin_lock(&tcon->open_file_lock);
cifs_stats_inc( cifs_stats_inc(
&tcon->stats.cifs_stats.num_oplock_brks); &tcon->stats.cifs_stats.num_oplock_brks);
if (smb2_tcon_has_lease(tcon, rsp, lw)) { if (smb2_tcon_has_lease(tcon, rsp)) {
spin_unlock(&tcon->open_file_lock); spin_unlock(&tcon->open_file_lock);
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
return true; return true;
} }
open = smb2_tcon_find_pending_open_lease(tcon,
rsp);
if (open) {
__u8 lease_key[SMB2_LEASE_KEY_SIZE];
struct tcon_link *tlink;
tlink = cifs_get_tlink(open->tlink);
memcpy(lease_key, open->lease_key,
SMB2_LEASE_KEY_SIZE);
spin_unlock(&tcon->open_file_lock);
spin_unlock(&cifs_tcp_ses_lock);
smb2_queue_pending_open_break(tlink,
lease_key,
rsp->NewLeaseState);
return true;
}
spin_unlock(&tcon->open_file_lock); spin_unlock(&tcon->open_file_lock);
if (tcon->crfid.is_valid && if (tcon->crfid.is_valid &&
...@@ -639,7 +671,6 @@ smb2_is_valid_lease_break(char *buffer) ...@@ -639,7 +671,6 @@ smb2_is_valid_lease_break(char *buffer)
} }
} }
spin_unlock(&cifs_tcp_ses_lock); spin_unlock(&cifs_tcp_ses_lock);
kfree(lw);
cifs_dbg(FYI, "Can not process lease break - no lease matched\n"); cifs_dbg(FYI, "Can not process lease break - no lease matched\n");
return false; return false;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment