Commit e53f31bf authored by Linus Torvalds's avatar Linus Torvalds
Browse files

Merge tag '5.1-rc5-smb3-fixes' of git://git.samba.org/sfrench/cifs-2.6

Pull smb3 fixes from Steve French:
 "Five small SMB3 fixes, all also for stable - an important fix for an
  oplock (lease) bug, a handle leak, and three bugs spotted by KASAN"

* tag '5.1-rc5-smb3-fixes' of git://git.samba.org/sfrench/cifs-2.6:
  CIFS: keep FileInfo handle live during oplock break
  cifs: fix handle leak in smb2_query_symlink()
  cifs: Fix lease buffer length error
  cifs: Fix use-after-free in SMB2_read
  cifs: Fix use-after-free in SMB2_write
parents fe5cdef2 b98749ca
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -1333,6 +1333,7 @@ cifsFileInfo_get_locked(struct cifsFileInfo *cifs_file)
}
}


struct cifsFileInfo *cifsFileInfo_get(struct cifsFileInfo *cifs_file);
struct cifsFileInfo *cifsFileInfo_get(struct cifsFileInfo *cifs_file);
void _cifsFileInfo_put(struct cifsFileInfo *cifs_file, bool wait_oplock_hdlr);
void cifsFileInfo_put(struct cifsFileInfo *cifs_file);
void cifsFileInfo_put(struct cifsFileInfo *cifs_file);


#define CIFS_CACHE_READ_FLG	1
#define CIFS_CACHE_READ_FLG	1
@@ -1855,6 +1856,7 @@ GLOBAL_EXTERN spinlock_t gidsidlock;
#endif /* CONFIG_CIFS_ACL */
#endif /* CONFIG_CIFS_ACL */


void cifs_oplock_break(struct work_struct *work);
void cifs_oplock_break(struct work_struct *work);
void cifs_queue_oplock_break(struct cifsFileInfo *cfile);


extern const struct slow_work_ops cifs_oplock_break_ops;
extern const struct slow_work_ops cifs_oplock_break_ops;
extern struct workqueue_struct *cifsiod_wq;
extern struct workqueue_struct *cifsiod_wq;
+25 −5
Original line number Original line Diff line number Diff line
@@ -360,12 +360,30 @@ cifsFileInfo_get(struct cifsFileInfo *cifs_file)
	return cifs_file;
	return cifs_file;
}
}


/*
/**
 * Release a reference on the file private data. This may involve closing
 * cifsFileInfo_put - release a reference of file priv data
 * the filehandle out on the server. Must be called without holding
 *
 * tcon->open_file_lock and cifs_file->file_info_lock.
 * Always potentially wait for oplock handler. See _cifsFileInfo_put().
 */
 */
void cifsFileInfo_put(struct cifsFileInfo *cifs_file)
void cifsFileInfo_put(struct cifsFileInfo *cifs_file)
{
	_cifsFileInfo_put(cifs_file, true);
}

/**
 * _cifsFileInfo_put - release a reference of file priv data
 *
 * This may involve closing the filehandle @cifs_file out on the
 * server. Must be called without holding tcon->open_file_lock and
 * cifs_file->file_info_lock.
 *
 * If @wait_for_oplock_handler is true and we are releasing the last
 * reference, wait for any running oplock break handler of the file
 * and cancel any pending one. If calling this function from the
 * oplock break handler, you need to pass false.
 *
 */
void _cifsFileInfo_put(struct cifsFileInfo *cifs_file, bool wait_oplock_handler)
{
{
	struct inode *inode = d_inode(cifs_file->dentry);
	struct inode *inode = d_inode(cifs_file->dentry);
	struct cifs_tcon *tcon = tlink_tcon(cifs_file->tlink);
	struct cifs_tcon *tcon = tlink_tcon(cifs_file->tlink);
@@ -414,7 +432,8 @@ void cifsFileInfo_put(struct cifsFileInfo *cifs_file)


	spin_unlock(&tcon->open_file_lock);
	spin_unlock(&tcon->open_file_lock);


	oplock_break_cancelled = cancel_work_sync(&cifs_file->oplock_break);
	oplock_break_cancelled = wait_oplock_handler ?
		cancel_work_sync(&cifs_file->oplock_break) : false;


	if (!tcon->need_reconnect && !cifs_file->invalidHandle) {
	if (!tcon->need_reconnect && !cifs_file->invalidHandle) {
		struct TCP_Server_Info *server = tcon->ses->server;
		struct TCP_Server_Info *server = tcon->ses->server;
@@ -4603,6 +4622,7 @@ void cifs_oplock_break(struct work_struct *work)
							     cinode);
							     cinode);
		cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
		cifs_dbg(FYI, "Oplock release rc = %d\n", rc);
	}
	}
	_cifsFileInfo_put(cfile, false /* do not wait for ourself */);
	cifs_done_oplock_break(cinode);
	cifs_done_oplock_break(cinode);
}
}


+23 −2
Original line number Original line Diff line number Diff line
@@ -501,8 +501,7 @@ is_valid_oplock_break(char *buffer, struct TCP_Server_Info *srv)
					   CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
					   CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
					   &pCifsInode->flags);
					   &pCifsInode->flags);


				queue_work(cifsoplockd_wq,
				cifs_queue_oplock_break(netfile);
					   &netfile->oplock_break);
				netfile->oplock_break_cancelled = false;
				netfile->oplock_break_cancelled = false;


				spin_unlock(&tcon->open_file_lock);
				spin_unlock(&tcon->open_file_lock);
@@ -607,6 +606,28 @@ void cifs_put_writer(struct cifsInodeInfo *cinode)
	spin_unlock(&cinode->writers_lock);
	spin_unlock(&cinode->writers_lock);
}
}


/**
 * cifs_queue_oplock_break - queue the oplock break handler for cfile
 *
 * This function is called from the demultiplex thread when it
 * receives an oplock break for @cfile.
 *
 * Assumes the tcon->open_file_lock is held.
 * Assumes cfile->file_info_lock is NOT held.
 */
void cifs_queue_oplock_break(struct cifsFileInfo *cfile)
{
	/*
	 * Bump the handle refcount now while we hold the
	 * open_file_lock to enforce the validity of it for the oplock
	 * break handler. The matching put is done at the end of the
	 * handler.
	 */
	cifsFileInfo_get(cfile);

	queue_work(cifsoplockd_wq, &cfile->oplock_break);
}

void cifs_done_oplock_break(struct cifsInodeInfo *cinode)
void cifs_done_oplock_break(struct cifsInodeInfo *cinode)
{
{
	clear_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags);
	clear_bit(CIFS_INODE_PENDING_OPLOCK_BREAK, &cinode->flags);
+3 −3
Original line number Original line Diff line number Diff line
@@ -555,7 +555,7 @@ smb2_tcon_has_lease(struct cifs_tcon *tcon, struct smb2_lease_break *rsp,
			clear_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
			clear_bit(CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
				  &cinode->flags);
				  &cinode->flags);


		queue_work(cifsoplockd_wq, &cfile->oplock_break);
		cifs_queue_oplock_break(cfile);
		kfree(lw);
		kfree(lw);
		return true;
		return true;
	}
	}
@@ -712,8 +712,8 @@ smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server)
					   CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
					   CIFS_INODE_DOWNGRADE_OPLOCK_TO_L2,
					   &cinode->flags);
					   &cinode->flags);
				spin_unlock(&cfile->file_info_lock);
				spin_unlock(&cfile->file_info_lock);
				queue_work(cifsoplockd_wq,

					   &cfile->oplock_break);
				cifs_queue_oplock_break(cfile);


				spin_unlock(&tcon->open_file_lock);
				spin_unlock(&tcon->open_file_lock);
				spin_unlock(&cifs_tcp_ses_lock);
				spin_unlock(&cifs_tcp_ses_lock);
+2 −0
Original line number Original line Diff line number Diff line
@@ -2389,6 +2389,8 @@ smb2_query_symlink(const unsigned int xid, struct cifs_tcon *tcon,


	rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, &err_iov,
	rc = SMB2_open(xid, &oparms, utf16_path, &oplock, NULL, &err_iov,
		       &resp_buftype);
		       &resp_buftype);
	if (!rc)
		SMB2_close(xid, tcon, fid.persistent_fid, fid.volatile_fid);
	if (!rc || !err_iov.iov_base) {
	if (!rc || !err_iov.iov_base) {
		rc = -ENOENT;
		rc = -ENOENT;
		goto free_path;
		goto free_path;
Loading