Merge branch 'for-next' of git://git.samba.org/sfrench/cifs-2.6
Pull cifs fixes from Steve French: "SMB3 "validate negotiate" is needed to prevent certain types of downgrade attacks. Also changes SMB2/SMB3 copy offload from using the BTRFS copy ioctl (BTRFS_IOC_CLONE) to a cifs specific ioctl (CIFS_IOC_COPYCHUNK_FILE) to address Christoph's comment that there are semantic differences between requesting copy offload in which copy-on-write is mandatory (as in the BTRFS ioctl) and optional in the SMB2/SMB3 case. Also fixes SMB2/SMB3 copychunk for large files" * 'for-next' of git://git.samba.org/sfrench/cifs-2.6: [CIFS] Do not use btrfs refcopy ioctl for SMB2 copy offload Check SMB3 dialects against downgrade attacks Removed duplicated (and unneeded) goto CIFS: Fix SMB2/SMB3 Copy offload support (refcopy) for large files
This commit is contained in:
@@ -384,6 +384,7 @@ struct smb_version_operations {
|
|||||||
int (*clone_range)(const unsigned int, struct cifsFileInfo *src_file,
|
int (*clone_range)(const unsigned int, struct cifsFileInfo *src_file,
|
||||||
struct cifsFileInfo *target_file, u64 src_off, u64 len,
|
struct cifsFileInfo *target_file, u64 src_off, u64 len,
|
||||||
u64 dest_off);
|
u64 dest_off);
|
||||||
|
int (*validate_negotiate)(const unsigned int, struct cifs_tcon *);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct smb_version_values {
|
struct smb_version_values {
|
||||||
|
@@ -26,13 +26,15 @@
|
|||||||
#include <linux/mount.h>
|
#include <linux/mount.h>
|
||||||
#include <linux/mm.h>
|
#include <linux/mm.h>
|
||||||
#include <linux/pagemap.h>
|
#include <linux/pagemap.h>
|
||||||
#include <linux/btrfs.h>
|
|
||||||
#include "cifspdu.h"
|
#include "cifspdu.h"
|
||||||
#include "cifsglob.h"
|
#include "cifsglob.h"
|
||||||
#include "cifsproto.h"
|
#include "cifsproto.h"
|
||||||
#include "cifs_debug.h"
|
#include "cifs_debug.h"
|
||||||
#include "cifsfs.h"
|
#include "cifsfs.h"
|
||||||
|
|
||||||
|
#define CIFS_IOCTL_MAGIC 0xCF
|
||||||
|
#define CIFS_IOC_COPYCHUNK_FILE _IOW(CIFS_IOCTL_MAGIC, 3, int)
|
||||||
|
|
||||||
static long cifs_ioctl_clone(unsigned int xid, struct file *dst_file,
|
static long cifs_ioctl_clone(unsigned int xid, struct file *dst_file,
|
||||||
unsigned long srcfd, u64 off, u64 len, u64 destoff)
|
unsigned long srcfd, u64 off, u64 len, u64 destoff)
|
||||||
{
|
{
|
||||||
@@ -213,7 +215,7 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg)
|
|||||||
cifs_dbg(FYI, "set compress flag rc %d\n", rc);
|
cifs_dbg(FYI, "set compress flag rc %d\n", rc);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BTRFS_IOC_CLONE:
|
case CIFS_IOC_COPYCHUNK_FILE:
|
||||||
rc = cifs_ioctl_clone(xid, filep, arg, 0, 0, 0);
|
rc = cifs_ioctl_clone(xid, filep, arg, 0, 0, 0);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@@ -532,7 +532,10 @@ smb2_clone_range(const unsigned int xid,
|
|||||||
int rc;
|
int rc;
|
||||||
unsigned int ret_data_len;
|
unsigned int ret_data_len;
|
||||||
struct copychunk_ioctl *pcchunk;
|
struct copychunk_ioctl *pcchunk;
|
||||||
char *retbuf = NULL;
|
struct copychunk_ioctl_rsp *retbuf = NULL;
|
||||||
|
struct cifs_tcon *tcon;
|
||||||
|
int chunks_copied = 0;
|
||||||
|
bool chunk_sizes_updated = false;
|
||||||
|
|
||||||
pcchunk = kmalloc(sizeof(struct copychunk_ioctl), GFP_KERNEL);
|
pcchunk = kmalloc(sizeof(struct copychunk_ioctl), GFP_KERNEL);
|
||||||
|
|
||||||
@@ -547,27 +550,96 @@ smb2_clone_range(const unsigned int xid,
|
|||||||
|
|
||||||
/* Note: request_res_key sets res_key null only if rc !=0 */
|
/* Note: request_res_key sets res_key null only if rc !=0 */
|
||||||
if (rc)
|
if (rc)
|
||||||
return rc;
|
goto cchunk_out;
|
||||||
|
|
||||||
/* For now array only one chunk long, will make more flexible later */
|
/* For now array only one chunk long, will make more flexible later */
|
||||||
pcchunk->ChunkCount = __constant_cpu_to_le32(1);
|
pcchunk->ChunkCount = __constant_cpu_to_le32(1);
|
||||||
pcchunk->Reserved = 0;
|
pcchunk->Reserved = 0;
|
||||||
pcchunk->SourceOffset = cpu_to_le64(src_off);
|
|
||||||
pcchunk->TargetOffset = cpu_to_le64(dest_off);
|
|
||||||
pcchunk->Length = cpu_to_le32(len);
|
|
||||||
pcchunk->Reserved2 = 0;
|
pcchunk->Reserved2 = 0;
|
||||||
|
|
||||||
/* Request that server copy to target from src file identified by key */
|
tcon = tlink_tcon(trgtfile->tlink);
|
||||||
rc = SMB2_ioctl(xid, tlink_tcon(trgtfile->tlink),
|
|
||||||
trgtfile->fid.persistent_fid,
|
while (len > 0) {
|
||||||
|
pcchunk->SourceOffset = cpu_to_le64(src_off);
|
||||||
|
pcchunk->TargetOffset = cpu_to_le64(dest_off);
|
||||||
|
pcchunk->Length =
|
||||||
|
cpu_to_le32(min_t(u32, len, tcon->max_bytes_chunk));
|
||||||
|
|
||||||
|
/* Request server copy to target from src identified by key */
|
||||||
|
rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid,
|
||||||
trgtfile->fid.volatile_fid, FSCTL_SRV_COPYCHUNK_WRITE,
|
trgtfile->fid.volatile_fid, FSCTL_SRV_COPYCHUNK_WRITE,
|
||||||
true /* is_fsctl */, (char *)pcchunk,
|
true /* is_fsctl */, (char *)pcchunk,
|
||||||
sizeof(struct copychunk_ioctl), &retbuf, &ret_data_len);
|
sizeof(struct copychunk_ioctl), (char **)&retbuf,
|
||||||
|
&ret_data_len);
|
||||||
|
if (rc == 0) {
|
||||||
|
if (ret_data_len !=
|
||||||
|
sizeof(struct copychunk_ioctl_rsp)) {
|
||||||
|
cifs_dbg(VFS, "invalid cchunk response size\n");
|
||||||
|
rc = -EIO;
|
||||||
|
goto cchunk_out;
|
||||||
|
}
|
||||||
|
if (retbuf->TotalBytesWritten == 0) {
|
||||||
|
cifs_dbg(FYI, "no bytes copied\n");
|
||||||
|
rc = -EIO;
|
||||||
|
goto cchunk_out;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Check if server claimed to write more than we asked
|
||||||
|
*/
|
||||||
|
if (le32_to_cpu(retbuf->TotalBytesWritten) >
|
||||||
|
le32_to_cpu(pcchunk->Length)) {
|
||||||
|
cifs_dbg(VFS, "invalid copy chunk response\n");
|
||||||
|
rc = -EIO;
|
||||||
|
goto cchunk_out;
|
||||||
|
}
|
||||||
|
if (le32_to_cpu(retbuf->ChunksWritten) != 1) {
|
||||||
|
cifs_dbg(VFS, "invalid num chunks written\n");
|
||||||
|
rc = -EIO;
|
||||||
|
goto cchunk_out;
|
||||||
|
}
|
||||||
|
chunks_copied++;
|
||||||
|
|
||||||
/* BB need to special case rc = EINVAL to alter chunk size */
|
src_off += le32_to_cpu(retbuf->TotalBytesWritten);
|
||||||
|
dest_off += le32_to_cpu(retbuf->TotalBytesWritten);
|
||||||
|
len -= le32_to_cpu(retbuf->TotalBytesWritten);
|
||||||
|
|
||||||
cifs_dbg(FYI, "rc %d data length out %d\n", rc, ret_data_len);
|
cifs_dbg(FYI, "Chunks %d PartialChunk %d Total %d\n",
|
||||||
|
le32_to_cpu(retbuf->ChunksWritten),
|
||||||
|
le32_to_cpu(retbuf->ChunkBytesWritten),
|
||||||
|
le32_to_cpu(retbuf->TotalBytesWritten));
|
||||||
|
} else if (rc == -EINVAL) {
|
||||||
|
if (ret_data_len != sizeof(struct copychunk_ioctl_rsp))
|
||||||
|
goto cchunk_out;
|
||||||
|
|
||||||
|
cifs_dbg(FYI, "MaxChunks %d BytesChunk %d MaxCopy %d\n",
|
||||||
|
le32_to_cpu(retbuf->ChunksWritten),
|
||||||
|
le32_to_cpu(retbuf->ChunkBytesWritten),
|
||||||
|
le32_to_cpu(retbuf->TotalBytesWritten));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if this is the first request using these sizes,
|
||||||
|
* (ie check if copy succeed once with original sizes
|
||||||
|
* and check if the server gave us different sizes after
|
||||||
|
* we already updated max sizes on previous request).
|
||||||
|
* if not then why is the server returning an error now
|
||||||
|
*/
|
||||||
|
if ((chunks_copied != 0) || chunk_sizes_updated)
|
||||||
|
goto cchunk_out;
|
||||||
|
|
||||||
|
/* Check that server is not asking us to grow size */
|
||||||
|
if (le32_to_cpu(retbuf->ChunkBytesWritten) <
|
||||||
|
tcon->max_bytes_chunk)
|
||||||
|
tcon->max_bytes_chunk =
|
||||||
|
le32_to_cpu(retbuf->ChunkBytesWritten);
|
||||||
|
else
|
||||||
|
goto cchunk_out; /* server gave us bogus size */
|
||||||
|
|
||||||
|
/* No need to change MaxChunks since already set to 1 */
|
||||||
|
chunk_sizes_updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cchunk_out:
|
||||||
kfree(pcchunk);
|
kfree(pcchunk);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@@ -1247,6 +1319,7 @@ struct smb_version_operations smb30_operations = {
|
|||||||
.create_lease_buf = smb3_create_lease_buf,
|
.create_lease_buf = smb3_create_lease_buf,
|
||||||
.parse_lease_buf = smb3_parse_lease_buf,
|
.parse_lease_buf = smb3_parse_lease_buf,
|
||||||
.clone_range = smb2_clone_range,
|
.clone_range = smb2_clone_range,
|
||||||
|
.validate_negotiate = smb3_validate_negotiate,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct smb_version_values smb20_values = {
|
struct smb_version_values smb20_values = {
|
||||||
|
@@ -454,6 +454,81 @@ neg_exit:
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int smb3_validate_negotiate(const unsigned int xid, struct cifs_tcon *tcon)
|
||||||
|
{
|
||||||
|
int rc = 0;
|
||||||
|
struct validate_negotiate_info_req vneg_inbuf;
|
||||||
|
struct validate_negotiate_info_rsp *pneg_rsp;
|
||||||
|
u32 rsplen;
|
||||||
|
|
||||||
|
cifs_dbg(FYI, "validate negotiate\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* validation ioctl must be signed, so no point sending this if we
|
||||||
|
* can not sign it. We could eventually change this to selectively
|
||||||
|
* sign just this, the first and only signed request on a connection.
|
||||||
|
* This is good enough for now since a user who wants better security
|
||||||
|
* would also enable signing on the mount. Having validation of
|
||||||
|
* negotiate info for signed connections helps reduce attack vectors
|
||||||
|
*/
|
||||||
|
if (tcon->ses->server->sign == false)
|
||||||
|
return 0; /* validation requires signing */
|
||||||
|
|
||||||
|
vneg_inbuf.Capabilities =
|
||||||
|
cpu_to_le32(tcon->ses->server->vals->req_capabilities);
|
||||||
|
memcpy(vneg_inbuf.Guid, cifs_client_guid, SMB2_CLIENT_GUID_SIZE);
|
||||||
|
|
||||||
|
if (tcon->ses->sign)
|
||||||
|
vneg_inbuf.SecurityMode =
|
||||||
|
cpu_to_le16(SMB2_NEGOTIATE_SIGNING_REQUIRED);
|
||||||
|
else if (global_secflags & CIFSSEC_MAY_SIGN)
|
||||||
|
vneg_inbuf.SecurityMode =
|
||||||
|
cpu_to_le16(SMB2_NEGOTIATE_SIGNING_ENABLED);
|
||||||
|
else
|
||||||
|
vneg_inbuf.SecurityMode = 0;
|
||||||
|
|
||||||
|
vneg_inbuf.DialectCount = cpu_to_le16(1);
|
||||||
|
vneg_inbuf.Dialects[0] =
|
||||||
|
cpu_to_le16(tcon->ses->server->vals->protocol_id);
|
||||||
|
|
||||||
|
rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID,
|
||||||
|
FSCTL_VALIDATE_NEGOTIATE_INFO, true /* is_fsctl */,
|
||||||
|
(char *)&vneg_inbuf, sizeof(struct validate_negotiate_info_req),
|
||||||
|
(char **)&pneg_rsp, &rsplen);
|
||||||
|
|
||||||
|
if (rc != 0) {
|
||||||
|
cifs_dbg(VFS, "validate protocol negotiate failed: %d\n", rc);
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rsplen != sizeof(struct validate_negotiate_info_rsp)) {
|
||||||
|
cifs_dbg(VFS, "invalid size of protocol negotiate response\n");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check validate negotiate info response matches what we got earlier */
|
||||||
|
if (pneg_rsp->Dialect !=
|
||||||
|
cpu_to_le16(tcon->ses->server->vals->protocol_id))
|
||||||
|
goto vneg_out;
|
||||||
|
|
||||||
|
if (pneg_rsp->SecurityMode != cpu_to_le16(tcon->ses->server->sec_mode))
|
||||||
|
goto vneg_out;
|
||||||
|
|
||||||
|
/* do not validate server guid because not saved at negprot time yet */
|
||||||
|
|
||||||
|
if ((le32_to_cpu(pneg_rsp->Capabilities) | SMB2_NT_FIND |
|
||||||
|
SMB2_LARGE_FILES) != tcon->ses->server->capabilities)
|
||||||
|
goto vneg_out;
|
||||||
|
|
||||||
|
/* validate negotiate successful */
|
||||||
|
cifs_dbg(FYI, "validate negotiate info successful\n");
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
vneg_out:
|
||||||
|
cifs_dbg(VFS, "protocol revalidation - security settings mismatch\n");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
|
SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses,
|
||||||
const struct nls_table *nls_cp)
|
const struct nls_table *nls_cp)
|
||||||
@@ -829,6 +904,8 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree,
|
|||||||
((tcon->share_flags & SHI1005_FLAGS_DFS) == 0))
|
((tcon->share_flags & SHI1005_FLAGS_DFS) == 0))
|
||||||
cifs_dbg(VFS, "DFS capability contradicts DFS flag\n");
|
cifs_dbg(VFS, "DFS capability contradicts DFS flag\n");
|
||||||
init_copy_chunk_defaults(tcon);
|
init_copy_chunk_defaults(tcon);
|
||||||
|
if (tcon->ses->server->ops->validate_negotiate)
|
||||||
|
rc = tcon->ses->server->ops->validate_negotiate(xid, tcon);
|
||||||
tcon_exit:
|
tcon_exit:
|
||||||
free_rsp_buf(resp_buftype, rsp);
|
free_rsp_buf(resp_buftype, rsp);
|
||||||
kfree(unc_path);
|
kfree(unc_path);
|
||||||
@@ -1214,10 +1291,17 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid,
|
|||||||
rc = SendReceive2(xid, ses, iov, num_iovecs, &resp_buftype, 0);
|
rc = SendReceive2(xid, ses, iov, num_iovecs, &resp_buftype, 0);
|
||||||
rsp = (struct smb2_ioctl_rsp *)iov[0].iov_base;
|
rsp = (struct smb2_ioctl_rsp *)iov[0].iov_base;
|
||||||
|
|
||||||
if (rc != 0) {
|
if ((rc != 0) && (rc != -EINVAL)) {
|
||||||
if (tcon)
|
if (tcon)
|
||||||
cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE);
|
cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE);
|
||||||
goto ioctl_exit;
|
goto ioctl_exit;
|
||||||
|
} else if (rc == -EINVAL) {
|
||||||
|
if ((opcode != FSCTL_SRV_COPYCHUNK_WRITE) &&
|
||||||
|
(opcode != FSCTL_SRV_COPYCHUNK)) {
|
||||||
|
if (tcon)
|
||||||
|
cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE);
|
||||||
|
goto ioctl_exit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check if caller wants to look at return data or just return rc */
|
/* check if caller wants to look at return data or just return rc */
|
||||||
@@ -2154,11 +2238,9 @@ send_set_info(const unsigned int xid, struct cifs_tcon *tcon,
|
|||||||
rc = SendReceive2(xid, ses, iov, num, &resp_buftype, 0);
|
rc = SendReceive2(xid, ses, iov, num, &resp_buftype, 0);
|
||||||
rsp = (struct smb2_set_info_rsp *)iov[0].iov_base;
|
rsp = (struct smb2_set_info_rsp *)iov[0].iov_base;
|
||||||
|
|
||||||
if (rc != 0) {
|
if (rc != 0)
|
||||||
cifs_stats_fail_inc(tcon, SMB2_SET_INFO_HE);
|
cifs_stats_fail_inc(tcon, SMB2_SET_INFO_HE);
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
out:
|
|
||||||
free_rsp_buf(resp_buftype, rsp);
|
free_rsp_buf(resp_buftype, rsp);
|
||||||
kfree(iov);
|
kfree(iov);
|
||||||
return rc;
|
return rc;
|
||||||
|
@@ -577,13 +577,19 @@ struct copychunk_ioctl_rsp {
|
|||||||
__le32 TotalBytesWritten;
|
__le32 TotalBytesWritten;
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
/* Response and Request are the same format */
|
struct validate_negotiate_info_req {
|
||||||
struct validate_negotiate_info {
|
|
||||||
__le32 Capabilities;
|
__le32 Capabilities;
|
||||||
__u8 Guid[SMB2_CLIENT_GUID_SIZE];
|
__u8 Guid[SMB2_CLIENT_GUID_SIZE];
|
||||||
__le16 SecurityMode;
|
__le16 SecurityMode;
|
||||||
__le16 DialectCount;
|
__le16 DialectCount;
|
||||||
__le16 Dialect[1];
|
__le16 Dialects[1]; /* dialect (someday maybe list) client asked for */
|
||||||
|
} __packed;
|
||||||
|
|
||||||
|
struct validate_negotiate_info_rsp {
|
||||||
|
__le32 Capabilities;
|
||||||
|
__u8 Guid[SMB2_CLIENT_GUID_SIZE];
|
||||||
|
__le16 SecurityMode;
|
||||||
|
__le16 Dialect; /* Dialect in use for the connection */
|
||||||
} __packed;
|
} __packed;
|
||||||
|
|
||||||
#define RSS_CAPABLE 0x00000001
|
#define RSS_CAPABLE 0x00000001
|
||||||
|
@@ -162,5 +162,6 @@ extern int smb2_lockv(const unsigned int xid, struct cifs_tcon *tcon,
|
|||||||
struct smb2_lock_element *buf);
|
struct smb2_lock_element *buf);
|
||||||
extern int SMB2_lease_break(const unsigned int xid, struct cifs_tcon *tcon,
|
extern int SMB2_lease_break(const unsigned int xid, struct cifs_tcon *tcon,
|
||||||
__u8 *lease_key, const __le32 lease_state);
|
__u8 *lease_key, const __le32 lease_state);
|
||||||
|
extern int smb3_validate_negotiate(const unsigned int, struct cifs_tcon *);
|
||||||
|
|
||||||
#endif /* _SMB2PROTO_H */
|
#endif /* _SMB2PROTO_H */
|
||||||
|
@@ -90,7 +90,7 @@
|
|||||||
#define FSCTL_LMR_REQUEST_RESILIENCY 0x001401D4 /* BB add struct */
|
#define FSCTL_LMR_REQUEST_RESILIENCY 0x001401D4 /* BB add struct */
|
||||||
#define FSCTL_LMR_GET_LINK_TRACK_INF 0x001400E8 /* BB add struct */
|
#define FSCTL_LMR_GET_LINK_TRACK_INF 0x001400E8 /* BB add struct */
|
||||||
#define FSCTL_LMR_SET_LINK_TRACK_INF 0x001400EC /* BB add struct */
|
#define FSCTL_LMR_SET_LINK_TRACK_INF 0x001400EC /* BB add struct */
|
||||||
#define FSCTL_VALIDATE_NEGOTIATE_INFO 0x00140204 /* BB add struct */
|
#define FSCTL_VALIDATE_NEGOTIATE_INFO 0x00140204
|
||||||
/* Perform server-side data movement */
|
/* Perform server-side data movement */
|
||||||
#define FSCTL_SRV_COPYCHUNK 0x001440F2
|
#define FSCTL_SRV_COPYCHUNK 0x001440F2
|
||||||
#define FSCTL_SRV_COPYCHUNK_WRITE 0x001480F2
|
#define FSCTL_SRV_COPYCHUNK_WRITE 0x001480F2
|
||||||
|
Reference in New Issue
Block a user