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

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

Pull smb client fixes from Steve French:
 "Two smb3 client fixes, both for stable:

   - Fix encryption problem with multiple compounded ops

   - Fix rename error cases that could lead to data corruption"

* tag 'v6.17-rc5-smb3-client-fixes' of git://git.samba.org/sfrench/cifs-2.6:
  smb: client: fix data loss due to broken rename(2)
  smb: client: fix compound alignment with encryption
parents 320475fb c5ea3065
Loading
Loading
Loading
Loading
+9 −4
Original line number Diff line number Diff line
@@ -87,7 +87,7 @@
#define SMB_INTERFACE_POLL_INTERVAL	600

/* maximum number of PDUs in one compound */
#define MAX_COMPOUND 7
#define MAX_COMPOUND 10

/*
 * Default number of credits to keep available for SMB3.
@@ -1882,9 +1882,12 @@ static inline bool is_replayable_error(int error)


/* cifs_get_writable_file() flags */
#define FIND_WR_ANY         0
#define FIND_WR_FSUID_ONLY  1
#define FIND_WR_WITH_DELETE 2
enum cifs_writable_file_flags {
	FIND_WR_ANY			= 0U,
	FIND_WR_FSUID_ONLY		= (1U << 0),
	FIND_WR_WITH_DELETE		= (1U << 1),
	FIND_WR_NO_PENDING_DELETE	= (1U << 2),
};

#define   MID_FREE 0
#define   MID_REQUEST_ALLOCATED 1
@@ -2343,6 +2346,8 @@ struct smb2_compound_vars {
	struct kvec qi_iov;
	struct kvec io_iov[SMB2_IOCTL_IOV_SIZE];
	struct kvec si_iov[SMB2_SET_INFO_IOV_SIZE];
	struct kvec unlink_iov[SMB2_SET_INFO_IOV_SIZE];
	struct kvec rename_iov[SMB2_SET_INFO_IOV_SIZE];
	struct kvec close_iov;
	struct smb2_file_rename_info_hdr rename_info;
	struct smb2_file_link_info_hdr link_info;
+17 −1
Original line number Diff line number Diff line
@@ -998,7 +998,10 @@ int cifs_open(struct inode *inode, struct file *file)

	/* Get the cached handle as SMB2 close is deferred */
	if (OPEN_FMODE(file->f_flags) & FMODE_WRITE) {
		rc = cifs_get_writable_path(tcon, full_path, FIND_WR_FSUID_ONLY, &cfile);
		rc = cifs_get_writable_path(tcon, full_path,
					    FIND_WR_FSUID_ONLY |
					    FIND_WR_NO_PENDING_DELETE,
					    &cfile);
	} else {
		rc = cifs_get_readable_path(tcon, full_path, &cfile);
	}
@@ -2530,6 +2533,9 @@ cifs_get_writable_file(struct cifsInodeInfo *cifs_inode, int flags,
			continue;
		if (with_delete && !(open_file->fid.access & DELETE))
			continue;
		if ((flags & FIND_WR_NO_PENDING_DELETE) &&
		    open_file->status_file_deleted)
			continue;
		if (OPEN_FMODE(open_file->f_flags) & FMODE_WRITE) {
			if (!open_file->invalidHandle) {
				/* found a good writable file */
@@ -2647,6 +2653,16 @@ cifs_get_readable_path(struct cifs_tcon *tcon, const char *name,
		spin_unlock(&tcon->open_file_lock);
		free_dentry_path(page);
		*ret_file = find_readable_file(cinode, 0);
		if (*ret_file) {
			spin_lock(&cinode->open_file_lock);
			if ((*ret_file)->status_file_deleted) {
				spin_unlock(&cinode->open_file_lock);
				cifsFileInfo_put(*ret_file);
				*ret_file = NULL;
			} else {
				spin_unlock(&cinode->open_file_lock);
			}
		}
		return *ret_file ? 0 : -ENOENT;
	}

+69 −17
Original line number Diff line number Diff line
@@ -1931,7 +1931,7 @@ cifs_drop_nlink(struct inode *inode)
 * but will return the EACCES to the caller. Note that the VFS does not call
 * unlink on negative dentries currently.
 */
int cifs_unlink(struct inode *dir, struct dentry *dentry)
static int __cifs_unlink(struct inode *dir, struct dentry *dentry, bool sillyrename)
{
	int rc = 0;
	unsigned int xid;
@@ -2003,6 +2003,10 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry)
		goto psx_del_no_retry;
	}

	if (sillyrename || (server->vals->protocol_id > SMB10_PROT_ID &&
			    d_is_positive(dentry) && d_count(dentry) > 2))
		rc = -EBUSY;
	else
		rc = server->ops->unlink(xid, tcon, full_path, cifs_sb, dentry);

psx_del_no_retry:
@@ -2071,6 +2075,11 @@ int cifs_unlink(struct inode *dir, struct dentry *dentry)
	return rc;
}

int cifs_unlink(struct inode *dir, struct dentry *dentry)
{
	return __cifs_unlink(dir, dentry, false);
}

static int
cifs_mkdir_qinfo(struct inode *parent, struct dentry *dentry, umode_t mode,
		 const char *full_path, struct cifs_sb_info *cifs_sb,
@@ -2358,14 +2367,16 @@ int cifs_rmdir(struct inode *inode, struct dentry *direntry)
	rc = server->ops->rmdir(xid, tcon, full_path, cifs_sb);
	cifs_put_tlink(tlink);

	cifsInode = CIFS_I(d_inode(direntry));

	if (!rc) {
		set_bit(CIFS_INO_DELETE_PENDING, &cifsInode->flags);
		spin_lock(&d_inode(direntry)->i_lock);
		i_size_write(d_inode(direntry), 0);
		clear_nlink(d_inode(direntry));
		spin_unlock(&d_inode(direntry)->i_lock);
	}

	cifsInode = CIFS_I(d_inode(direntry));
	/* force revalidate to go get info when needed */
	cifsInode->time = 0;

@@ -2458,8 +2469,11 @@ cifs_do_rename(const unsigned int xid, struct dentry *from_dentry,
	}
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */
do_rename_exit:
	if (rc == 0)
	if (rc == 0) {
		d_move(from_dentry, to_dentry);
		/* Force a new lookup */
		d_drop(from_dentry);
	}
	cifs_put_tlink(tlink);
	return rc;
}
@@ -2470,6 +2484,7 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
	     struct dentry *target_dentry, unsigned int flags)
{
	const char *from_name, *to_name;
	struct TCP_Server_Info *server;
	void *page1, *page2;
	struct cifs_sb_info *cifs_sb;
	struct tcon_link *tlink;
@@ -2505,6 +2520,7 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,
	if (IS_ERR(tlink))
		return PTR_ERR(tlink);
	tcon = tlink_tcon(tlink);
	server = tcon->ses->server;

	page1 = alloc_dentry_path();
	page2 = alloc_dentry_path();
@@ -2591,20 +2607,54 @@ cifs_rename2(struct mnt_idmap *idmap, struct inode *source_dir,

unlink_target:
#endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */

	/* Try unlinking the target dentry if it's not negative */
	if (d_really_is_positive(target_dentry) && (rc == -EACCES || rc == -EEXIST)) {
		if (d_is_dir(target_dentry))
	if (d_really_is_positive(target_dentry)) {
		if (!rc) {
			struct inode *inode = d_inode(target_dentry);
			/*
			 * Samba and ksmbd servers allow renaming a target
			 * directory that is open, so make sure to update
			 * ->i_nlink and then mark it as delete pending.
			 */
			if (S_ISDIR(inode->i_mode)) {
				drop_cached_dir_by_name(xid, tcon, to_name, cifs_sb);
				spin_lock(&inode->i_lock);
				i_size_write(inode, 0);
				clear_nlink(inode);
				spin_unlock(&inode->i_lock);
				set_bit(CIFS_INO_DELETE_PENDING, &CIFS_I(inode)->flags);
				CIFS_I(inode)->time = 0; /* force reval */
				inode_set_ctime_current(inode);
				inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
			}
		} else if (rc == -EACCES || rc == -EEXIST) {
			/*
			 * Rename failed, possibly due to a busy target.
			 * Retry it by unliking the target first.
			 */
			if (d_is_dir(target_dentry)) {
				tmprc = cifs_rmdir(target_dir, target_dentry);
		else
			tmprc = cifs_unlink(target_dir, target_dentry);
		if (tmprc)
			} else {
				tmprc = __cifs_unlink(target_dir, target_dentry,
						      server->vals->protocol_id > SMB10_PROT_ID);
			}
			if (tmprc) {
				/*
				 * Some servers will return STATUS_ACCESS_DENIED
				 * or STATUS_DIRECTORY_NOT_EMPTY when failing to
				 * rename a non-empty directory.  Make sure to
				 * propagate the appropriate error back to
				 * userspace.
				 */
				if (tmprc == -EEXIST || tmprc == -ENOTEMPTY)
					rc = tmprc;
				goto cifs_rename_exit;
			}
			rc = cifs_do_rename(xid, source_dentry, from_name,
					    target_dentry, to_name);
			if (!rc)
				rehash = false;
		}
	}

	/* force revalidate to go get info when needed */
	CIFS_I(source_dir)->time = CIFS_I(target_dir)->time = 0;
@@ -2629,6 +2679,8 @@ cifs_dentry_needs_reval(struct dentry *dentry)
	struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb);
	struct cached_fid *cfid = NULL;

	if (test_bit(CIFS_INO_DELETE_PENDING, &cifs_i->flags))
		return false;
	if (cifs_i->time == 0)
		return true;

+1 −2
Original line number Diff line number Diff line
@@ -30,10 +30,9 @@ enum smb2_compound_ops {
	SMB2_OP_QUERY_DIR,
	SMB2_OP_MKDIR,
	SMB2_OP_RENAME,
	SMB2_OP_DELETE,
	SMB2_OP_HARDLINK,
	SMB2_OP_SET_EOF,
	SMB2_OP_RMDIR,
	SMB2_OP_UNLINK,
	SMB2_OP_POSIX_QUERY_INFO,
	SMB2_OP_SET_REPARSE,
	SMB2_OP_GET_REPARSE,
+162 −42
Original line number Diff line number Diff line
@@ -346,9 +346,6 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
			trace_smb3_posix_query_info_compound_enter(xid, tcon->tid,
								   ses->Suid, full_path);
			break;
		case SMB2_OP_DELETE:
			trace_smb3_delete_enter(xid, tcon->tid, ses->Suid, full_path);
			break;
		case SMB2_OP_MKDIR:
			/*
			 * Directories are created through parameters in the
@@ -356,23 +353,40 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
			 */
			trace_smb3_mkdir_enter(xid, tcon->tid, ses->Suid, full_path);
			break;
		case SMB2_OP_RMDIR:
			rqst[num_rqst].rq_iov = &vars->si_iov[0];
		case SMB2_OP_UNLINK:
			rqst[num_rqst].rq_iov = vars->unlink_iov;
			rqst[num_rqst].rq_nvec = 1;

			size[0] = 1; /* sizeof __u8 See MS-FSCC section 2.4.11 */
			data[0] = &delete_pending[0];

			if (cfile) {
				rc = SMB2_set_info_init(tcon, server,
						&rqst[num_rqst], COMPOUND_FID,
						COMPOUND_FID, current->tgid,
							&rqst[num_rqst],
							cfile->fid.persistent_fid,
							cfile->fid.volatile_fid,
							current->tgid,
							FILE_DISPOSITION_INFORMATION,
						SMB2_O_INFO_FILE, 0, data, size);
			if (rc)
				goto finished;
							SMB2_O_INFO_FILE, 0,
							data, size);
			} else {
				rc = SMB2_set_info_init(tcon, server,
							&rqst[num_rqst],
							COMPOUND_FID,
							COMPOUND_FID,
							current->tgid,
							FILE_DISPOSITION_INFORMATION,
							SMB2_O_INFO_FILE, 0,
							data, size);
			}
			if (!rc && (!cfile || num_rqst > 1)) {
				smb2_set_next_command(tcon, &rqst[num_rqst]);
			smb2_set_related(&rqst[num_rqst++]);
			trace_smb3_rmdir_enter(xid, tcon->tid, ses->Suid, full_path);
				smb2_set_related(&rqst[num_rqst]);
			} else if (rc) {
				goto finished;
			}
			num_rqst++;
			trace_smb3_unlink_enter(xid, tcon->tid, ses->Suid, full_path);
			break;
		case SMB2_OP_SET_EOF:
			rqst[num_rqst].rq_iov = &vars->si_iov[0];
@@ -442,7 +456,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
							   ses->Suid, full_path);
			break;
		case SMB2_OP_RENAME:
			rqst[num_rqst].rq_iov = &vars->si_iov[0];
			rqst[num_rqst].rq_iov = vars->rename_iov;
			rqst[num_rqst].rq_nvec = 2;

			len = in_iov[i].iov_len;
@@ -732,19 +746,6 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
				trace_smb3_posix_query_info_compound_done(xid, tcon->tid,
									  ses->Suid);
			break;
		case SMB2_OP_DELETE:
			if (rc)
				trace_smb3_delete_err(xid, tcon->tid, ses->Suid, rc);
			else {
				/*
				 * If dentry (hence, inode) is NULL, lease break is going to
				 * take care of degrading leases on handles for deleted files.
				 */
				if (inode)
					cifs_mark_open_handles_for_deleted_file(inode, full_path);
				trace_smb3_delete_done(xid, tcon->tid, ses->Suid);
			}
			break;
		case SMB2_OP_MKDIR:
			if (rc)
				trace_smb3_mkdir_err(xid, tcon->tid, ses->Suid, rc);
@@ -765,11 +766,11 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon,
				trace_smb3_rename_done(xid, tcon->tid, ses->Suid);
			SMB2_set_info_free(&rqst[num_rqst++]);
			break;
		case SMB2_OP_RMDIR:
			if (rc)
				trace_smb3_rmdir_err(xid, tcon->tid, ses->Suid, rc);
		case SMB2_OP_UNLINK:
			if (!rc)
				trace_smb3_unlink_done(xid, tcon->tid, ses->Suid);
			else
				trace_smb3_rmdir_done(xid, tcon->tid, ses->Suid);
				trace_smb3_unlink_err(xid, tcon->tid, ses->Suid, rc);
			SMB2_set_info_free(&rqst[num_rqst++]);
			break;
		case SMB2_OP_SET_EOF:
@@ -1166,7 +1167,7 @@ smb2_rmdir(const unsigned int xid, struct cifs_tcon *tcon, const char *name,
			     FILE_OPEN, CREATE_NOT_FILE, ACL_NO_MODE);
	return smb2_compound_op(xid, tcon, cifs_sb,
				name, &oparms, NULL,
				&(int){SMB2_OP_RMDIR}, 1,
				&(int){SMB2_OP_UNLINK}, 1,
				NULL, NULL, NULL, NULL);
}

@@ -1175,20 +1176,29 @@ smb2_unlink(const unsigned int xid, struct cifs_tcon *tcon, const char *name,
	    struct cifs_sb_info *cifs_sb, struct dentry *dentry)
{
	struct cifs_open_parms oparms;
	struct inode *inode = NULL;
	int rc;

	oparms = CIFS_OPARMS(cifs_sb, tcon, name,
			     DELETE, FILE_OPEN,
			     CREATE_DELETE_ON_CLOSE | OPEN_REPARSE_POINT,
			     ACL_NO_MODE);
	int rc = smb2_compound_op(xid, tcon, cifs_sb, name, &oparms,
				  NULL, &(int){SMB2_OP_DELETE}, 1,
				  NULL, NULL, NULL, dentry);
	if (dentry)
		inode = d_inode(dentry);

	oparms = CIFS_OPARMS(cifs_sb, tcon, name, DELETE,
			     FILE_OPEN, OPEN_REPARSE_POINT, ACL_NO_MODE);
	rc = smb2_compound_op(xid, tcon, cifs_sb, name, &oparms,
			      NULL, &(int){SMB2_OP_UNLINK},
			      1, NULL, NULL, NULL, dentry);
	if (rc == -EINVAL) {
		cifs_dbg(FYI, "invalid lease key, resending request without lease");
		rc = smb2_compound_op(xid, tcon, cifs_sb, name, &oparms,
				      NULL, &(int){SMB2_OP_DELETE}, 1,
				      NULL, NULL, NULL, NULL);
				      NULL, &(int){SMB2_OP_UNLINK},
				      1, NULL, NULL, NULL, NULL);
	}
	/*
	 * If dentry (hence, inode) is NULL, lease break is going to
	 * take care of degrading leases on handles for deleted files.
	 */
	if (!rc && inode)
		cifs_mark_open_handles_for_deleted_file(inode, name);
	return rc;
}

@@ -1441,3 +1451,113 @@ int smb2_query_reparse_point(const unsigned int xid,
	cifs_free_open_info(&data);
	return rc;
}

static inline __le16 *utf16_smb2_path(struct cifs_sb_info *cifs_sb,
				      const char *name, size_t namelen)
{
	int len;

	if (*name == '\\' ||
	    (cifs_sb_master_tlink(cifs_sb) &&
	     cifs_sb_master_tcon(cifs_sb)->posix_extensions && *name == '/'))
		name++;
	return cifs_strndup_to_utf16(name, namelen, &len,
				     cifs_sb->local_nls,
				     cifs_remap(cifs_sb));
}

int smb2_rename_pending_delete(const char *full_path,
			       struct dentry *dentry,
			       const unsigned int xid)
{
	struct cifs_sb_info *cifs_sb = CIFS_SB(d_inode(dentry)->i_sb);
	struct cifsInodeInfo *cinode = CIFS_I(d_inode(dentry));
	__le16 *utf16_path __free(kfree) = NULL;
	__u32 co = file_create_options(dentry);
	int cmds[] = {
		SMB2_OP_SET_INFO,
		SMB2_OP_RENAME,
		SMB2_OP_UNLINK,
	};
	const int num_cmds = ARRAY_SIZE(cmds);
	char *to_name __free(kfree) = NULL;
	__u32 attrs = cinode->cifsAttrs;
	struct cifs_open_parms oparms;
	static atomic_t sillycounter;
	struct cifsFileInfo *cfile;
	struct tcon_link *tlink;
	struct cifs_tcon *tcon;
	struct kvec iov[2];
	const char *ppath;
	void *page;
	size_t len;
	int rc;

	tlink = cifs_sb_tlink(cifs_sb);
	if (IS_ERR(tlink))
		return PTR_ERR(tlink);
	tcon = tlink_tcon(tlink);

	page = alloc_dentry_path();

	ppath = build_path_from_dentry(dentry->d_parent, page);
	if (IS_ERR(ppath)) {
		rc = PTR_ERR(ppath);
		goto out;
	}

	len = strlen(ppath) + strlen("/.__smb1234") + 1;
	to_name = kmalloc(len, GFP_KERNEL);
	if (!to_name) {
		rc = -ENOMEM;
		goto out;
	}

	scnprintf(to_name, len, "%s%c.__smb%04X", ppath, CIFS_DIR_SEP(cifs_sb),
		  atomic_inc_return(&sillycounter) & 0xffff);

	utf16_path = utf16_smb2_path(cifs_sb, to_name, len);
	if (!utf16_path) {
		rc = -ENOMEM;
		goto out;
	}

	drop_cached_dir_by_name(xid, tcon, full_path, cifs_sb);
	oparms = CIFS_OPARMS(cifs_sb, tcon, full_path,
			     DELETE | FILE_WRITE_ATTRIBUTES,
			     FILE_OPEN, co, ACL_NO_MODE);

	attrs &= ~ATTR_READONLY;
	if (!attrs)
		attrs = ATTR_NORMAL;
	if (d_inode(dentry)->i_nlink <= 1)
		attrs |= ATTR_HIDDEN;
	iov[0].iov_base = &(FILE_BASIC_INFO) {
		.Attributes = cpu_to_le32(attrs),
	};
	iov[0].iov_len = sizeof(FILE_BASIC_INFO);
	iov[1].iov_base = utf16_path;
	iov[1].iov_len = sizeof(*utf16_path) * UniStrlen((wchar_t *)utf16_path);

	cifs_get_writable_path(tcon, full_path, FIND_WR_WITH_DELETE, &cfile);
	rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, iov,
			      cmds, num_cmds, cfile, NULL, NULL, dentry);
	if (rc == -EINVAL) {
		cifs_dbg(FYI, "invalid lease key, resending request without lease\n");
		cifs_get_writable_path(tcon, full_path,
				       FIND_WR_WITH_DELETE, &cfile);
		rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, iov,
				      cmds, num_cmds, cfile, NULL, NULL, NULL);
	}
	if (!rc) {
		set_bit(CIFS_INO_DELETE_PENDING, &cinode->flags);
	} else {
		cifs_tcon_dbg(FYI, "%s: failed to rename '%s' to '%s': %d\n",
			      __func__, full_path, to_name, rc);
		rc = -EIO;
	}
out:
	cifs_put_tlink(tlink);
	free_dentry_path(page);
	return rc;
}
Loading