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
Unified · Split
| @@ -384,6 +384,7 @@ struct smb_version_operations { · 383 unmodified lines | ||
| 384 | 384 | int (*clone_range)(const unsigned int, struct cifsFileInfo *src_file, |
| 385 | 385 | struct cifsFileInfo *target_file, u64 src_off, u64 len, |
| 386 | 386 | u64 dest_off); |
| 387 | + int (*validate_negotiate)(const unsigned int, struct cifs_tcon *); | |
| 387 | 388 | }; |
| 388 | 389 | |
| 389 | 390 | struct smb_version_values { |
| @@ -26,13 +26,15 @@ · 25 unmodified lines | ||
| 26 | 26 | #include <linux/mount.h> |
| 27 | 27 | #include <linux/mm.h> |
| 28 | 28 | #include <linux/pagemap.h> |
| 29 | −#include <linux/btrfs.h> | |
| 30 | 29 | #include "cifspdu.h" |
| 31 | 30 | #include "cifsglob.h" |
| 32 | 31 | #include "cifsproto.h" |
| 33 | 32 | #include "cifs_debug.h" |
| 34 | 33 | #include "cifsfs.h" |
| 35 | 34 | |
| 35 | +#define CIFS_IOCTL_MAGIC 0xCF | |
| 36 | +#define CIFS_IOC_COPYCHUNK_FILE _IOW(CIFS_IOCTL_MAGIC, 3, int) | |
| 37 | + | |
| 36 | 38 | static long cifs_ioctl_clone(unsigned int xid, struct file *dst_file, |
| 37 | 39 | unsigned long srcfd, u64 off, u64 len, u64 destoff) |
| 38 | 40 | { |
| @@ -213,7 +215,7 @@ long cifs_ioctl(struct file *filep, unsigned int command, unsigned long arg) · 174 unmodified lines | ||
| 213 | 215 | cifs_dbg(FYI, "set compress flag rc %d\n", rc); |
| 214 | 216 | } |
| 215 | 217 | break; |
| 216 | − case BTRFS_IOC_CLONE: | |
| 218 | + case CIFS_IOC_COPYCHUNK_FILE: | |
| 217 | 219 | rc = cifs_ioctl_clone(xid, filep, arg, 0, 0, 0); |
| 218 | 220 | break; |
| 219 | 221 | default: |
| @@ -532,7 +532,10 @@ smb2_clone_range(const unsigned int xid, · 531 unmodified lines | ||
| 532 | 532 | int rc; |
| 533 | 533 | unsigned int ret_data_len; |
| 534 | 534 | struct copychunk_ioctl *pcchunk; |
| 535 | − char *retbuf = NULL; | |
| 535 | + struct copychunk_ioctl_rsp *retbuf = NULL; | |
| 536 | + struct cifs_tcon *tcon; | |
| 537 | + int chunks_copied = 0; | |
| 538 | + bool chunk_sizes_updated = false; | |
| 536 | 539 | |
| 537 | 540 | pcchunk = kmalloc(sizeof(struct copychunk_ioctl), GFP_KERNEL); |
| 538 | 541 | |
| @@ -547,27 +550,96 @@ smb2_clone_range(const unsigned int xid, · 8 unmodified lines | ||
| 547 | 550 | |
| 548 | 551 | /* Note: request_res_key sets res_key null only if rc !=0 */ |
| 549 | 552 | if (rc) |
| 550 | − return rc; | |
| 553 | + goto cchunk_out; | |
| 551 | 554 | |
| 552 | 555 | /* For now array only one chunk long, will make more flexible later */ |
| 553 | 556 | pcchunk->ChunkCount = __constant_cpu_to_le32(1); |
| 554 | 557 | pcchunk->Reserved = 0; |
| 555 | − pcchunk->SourceOffset = cpu_to_le64(src_off); | |
| 556 | − pcchunk->TargetOffset = cpu_to_le64(dest_off); | |
| 557 | − pcchunk->Length = cpu_to_le32(len); | |
| 558 | 558 | pcchunk->Reserved2 = 0; |
| 559 | 559 | |
| 560 | − /* Request that server copy to target from src file identified by key */ | |
| 561 | − rc = SMB2_ioctl(xid, tlink_tcon(trgtfile->tlink), | |
| 562 | − trgtfile->fid.persistent_fid, | |
| 563 | − trgtfile->fid.volatile_fid, FSCTL_SRV_COPYCHUNK_WRITE, | |
| 564 | − true /* is_fsctl */, (char *)pcchunk, | |
| 565 | − sizeof(struct copychunk_ioctl), &retbuf, &ret_data_len); | |
| 560 | + tcon = tlink_tcon(trgtfile->tlink); | |
| 566 | 561 | |
| 567 | − /* BB need to special case rc = EINVAL to alter chunk size */ | |
| 562 | + while (len > 0) { | |
| 563 | + pcchunk->SourceOffset = cpu_to_le64(src_off); | |
| 564 | + pcchunk->TargetOffset = cpu_to_le64(dest_off); | |
| 565 | + pcchunk->Length = | |
| 566 | + cpu_to_le32(min_t(u32, len, tcon->max_bytes_chunk)); | |
| 568 | 567 | |
| 569 | − cifs_dbg(FYI, "rc %d data length out %d\n", rc, ret_data_len); | |
| 568 | + /* Request server copy to target from src identified by key */ | |
| 569 | + rc = SMB2_ioctl(xid, tcon, trgtfile->fid.persistent_fid, | |
| 570 | + trgtfile->fid.volatile_fid, FSCTL_SRV_COPYCHUNK_WRITE, | |
| 571 | + true /* is_fsctl */, (char *)pcchunk, | |
| 572 | + sizeof(struct copychunk_ioctl), (char **)&retbuf, | |
| 573 | + &ret_data_len); | |
| 574 | + if (rc == 0) { | |
| 575 | + if (ret_data_len != | |
| 576 | + sizeof(struct copychunk_ioctl_rsp)) { | |
| 577 | + cifs_dbg(VFS, "invalid cchunk response size\n"); | |
| 578 | + rc = -EIO; | |
| 579 | + goto cchunk_out; | |
| 580 | + } | |
| 581 | + if (retbuf->TotalBytesWritten == 0) { | |
| 582 | + cifs_dbg(FYI, "no bytes copied\n"); | |
| 583 | + rc = -EIO; | |
| 584 | + goto cchunk_out; | |
| 585 | + } | |
| 586 | + /* | |
| 587 | + * Check if server claimed to write more than we asked | |
| 588 | + */ | |
| 589 | + if (le32_to_cpu(retbuf->TotalBytesWritten) > | |
| 590 | + le32_to_cpu(pcchunk->Length)) { | |
| 591 | + cifs_dbg(VFS, "invalid copy chunk response\n"); | |
| 592 | + rc = -EIO; | |
| 593 | + goto cchunk_out; | |
| 594 | + } | |
| 595 | + if (le32_to_cpu(retbuf->ChunksWritten) != 1) { | |
| 596 | + cifs_dbg(VFS, "invalid num chunks written\n"); | |
| 597 | + rc = -EIO; | |
| 598 | + goto cchunk_out; | |
| 599 | + } | |
| 600 | + chunks_copied++; | |
| 601 | + | |
| 602 | + src_off += le32_to_cpu(retbuf->TotalBytesWritten); | |
| 603 | + dest_off += le32_to_cpu(retbuf->TotalBytesWritten); | |
| 604 | + len -= le32_to_cpu(retbuf->TotalBytesWritten); | |
| 605 | + | |
| 606 | + cifs_dbg(FYI, "Chunks %d PartialChunk %d Total %d\n", | |
| 607 | + le32_to_cpu(retbuf->ChunksWritten), | |
| 608 | + le32_to_cpu(retbuf->ChunkBytesWritten), | |
| 609 | + le32_to_cpu(retbuf->TotalBytesWritten)); | |
| 610 | + } else if (rc == -EINVAL) { | |
| 611 | + if (ret_data_len != sizeof(struct copychunk_ioctl_rsp)) | |
| 612 | + goto cchunk_out; | |
| 613 | + | |
| 614 | + cifs_dbg(FYI, "MaxChunks %d BytesChunk %d MaxCopy %d\n", | |
| 615 | + le32_to_cpu(retbuf->ChunksWritten), | |
| 616 | + le32_to_cpu(retbuf->ChunkBytesWritten), | |
| 617 | + le32_to_cpu(retbuf->TotalBytesWritten)); | |
| 618 | + | |
| 619 | + /* | |
| 620 | + * Check if this is the first request using these sizes, | |
| 621 | + * (ie check if copy succeed once with original sizes | |
| 622 | + * and check if the server gave us different sizes after | |
| 623 | + * we already updated max sizes on previous request). | |
| 624 | + * if not then why is the server returning an error now | |
| 625 | + */ | |
| 626 | + if ((chunks_copied != 0) || chunk_sizes_updated) | |
| 627 | + goto cchunk_out; | |
| 628 | + | |
| 629 | + /* Check that server is not asking us to grow size */ | |
| 630 | + if (le32_to_cpu(retbuf->ChunkBytesWritten) < | |
| 631 | + tcon->max_bytes_chunk) | |
| 632 | + tcon->max_bytes_chunk = | |
| 633 | + le32_to_cpu(retbuf->ChunkBytesWritten); | |
| 634 | + else | |
| 635 | + goto cchunk_out; /* server gave us bogus size */ | |
| 636 | + | |
| 637 | + /* No need to change MaxChunks since already set to 1 */ | |
| 638 | + chunk_sizes_updated = true; | |
| 639 | + } | |
| 640 | + } | |
| 570 | 641 | |
| 642 | +cchunk_out: | |
| 571 | 643 | kfree(pcchunk); |
| 572 | 644 | return rc; |
| 573 | 645 | } |
| @@ -1247,6 +1319,7 @@ struct smb_version_operations smb30_operations = { · 673 unmodified lines | ||
| 1247 | 1319 | .create_lease_buf = smb3_create_lease_buf, |
| 1248 | 1320 | .parse_lease_buf = smb3_parse_lease_buf, |
| 1249 | 1321 | .clone_range = smb2_clone_range, |
| 1322 | + .validate_negotiate = smb3_validate_negotiate, | |
| 1250 | 1323 | }; |
| 1251 | 1324 | |
| 1252 | 1325 | struct smb_version_values smb20_values = { |
| @@ -454,6 +454,81 @@ neg_exit: · 453 unmodified lines | ||
| 454 | 454 | return rc; |
| 455 | 455 | } |
| 456 | 456 | |
| 457 | +int smb3_validate_negotiate(const unsigned int xid, struct cifs_tcon *tcon) | |
| 458 | +{ | |
| 459 | + int rc = 0; | |
| 460 | + struct validate_negotiate_info_req vneg_inbuf; | |
| 461 | + struct validate_negotiate_info_rsp *pneg_rsp; | |
| 462 | + u32 rsplen; | |
| 463 | + | |
| 464 | + cifs_dbg(FYI, "validate negotiate\n"); | |
| 465 | + | |
| 466 | + /* | |
| 467 | + * validation ioctl must be signed, so no point sending this if we | |
| 468 | + * can not sign it. We could eventually change this to selectively | |
| 469 | + * sign just this, the first and only signed request on a connection. | |
| 470 | + * This is good enough for now since a user who wants better security | |
| 471 | + * would also enable signing on the mount. Having validation of | |
| 472 | + * negotiate info for signed connections helps reduce attack vectors | |
| 473 | + */ | |
| 474 | + if (tcon->ses->server->sign == false) | |
| 475 | + return 0; /* validation requires signing */ | |
| 476 | + | |
| 477 | + vneg_inbuf.Capabilities = | |
| 478 | + cpu_to_le32(tcon->ses->server->vals->req_capabilities); | |
| 479 | + memcpy(vneg_inbuf.Guid, cifs_client_guid, SMB2_CLIENT_GUID_SIZE); | |
| 480 | + | |
| 481 | + if (tcon->ses->sign) | |
| 482 | + vneg_inbuf.SecurityMode = | |
| 483 | + cpu_to_le16(SMB2_NEGOTIATE_SIGNING_REQUIRED); | |
| 484 | + else if (global_secflags & CIFSSEC_MAY_SIGN) | |
| 485 | + vneg_inbuf.SecurityMode = | |
| 486 | + cpu_to_le16(SMB2_NEGOTIATE_SIGNING_ENABLED); | |
| 487 | + else | |
| 488 | + vneg_inbuf.SecurityMode = 0; | |
| 489 | + | |
| 490 | + vneg_inbuf.DialectCount = cpu_to_le16(1); | |
| 491 | + vneg_inbuf.Dialects[0] = | |
| 492 | + cpu_to_le16(tcon->ses->server->vals->protocol_id); | |
| 493 | + | |
| 494 | + rc = SMB2_ioctl(xid, tcon, NO_FILE_ID, NO_FILE_ID, | |
| 495 | + FSCTL_VALIDATE_NEGOTIATE_INFO, true /* is_fsctl */, | |
| 496 | + (char *)&vneg_inbuf, sizeof(struct validate_negotiate_info_req), | |
| 497 | + (char **)&pneg_rsp, &rsplen); | |
| 498 | + | |
| 499 | + if (rc != 0) { | |
| 500 | + cifs_dbg(VFS, "validate protocol negotiate failed: %d\n", rc); | |
| 501 | + return -EIO; | |
| 502 | + } | |
| 503 | + | |
| 504 | + if (rsplen != sizeof(struct validate_negotiate_info_rsp)) { | |
| 505 | + cifs_dbg(VFS, "invalid size of protocol negotiate response\n"); | |
| 506 | + return -EIO; | |
| 507 | + } | |
| 508 | + | |
| 509 | + /* check validate negotiate info response matches what we got earlier */ | |
| 510 | + if (pneg_rsp->Dialect != | |
| 511 | + cpu_to_le16(tcon->ses->server->vals->protocol_id)) | |
| 512 | + goto vneg_out; | |
| 513 | + | |
| 514 | + if (pneg_rsp->SecurityMode != cpu_to_le16(tcon->ses->server->sec_mode)) | |
| 515 | + goto vneg_out; | |
| 516 | + | |
| 517 | + /* do not validate server guid because not saved at negprot time yet */ | |
| 518 | + | |
| 519 | + if ((le32_to_cpu(pneg_rsp->Capabilities) | SMB2_NT_FIND | | |
| 520 | + SMB2_LARGE_FILES) != tcon->ses->server->capabilities) | |
| 521 | + goto vneg_out; | |
| 522 | + | |
| 523 | + /* validate negotiate successful */ | |
| 524 | + cifs_dbg(FYI, "validate negotiate info successful\n"); | |
| 525 | + return 0; | |
| 526 | + | |
| 527 | +vneg_out: | |
| 528 | + cifs_dbg(VFS, "protocol revalidation - security settings mismatch\n"); | |
| 529 | + return -EIO; | |
| 530 | +} | |
| 531 | + | |
| 457 | 532 | int |
| 458 | 533 | SMB2_sess_setup(const unsigned int xid, struct cifs_ses *ses, |
| 459 | 534 | const struct nls_table *nls_cp) |
| @@ -829,6 +904,8 @@ SMB2_tcon(const unsigned int xid, struct cifs_ses *ses, const char *tree, · 369 unmodified lines | ||
| 829 | 904 | ((tcon->share_flags & SHI1005_FLAGS_DFS) == 0)) |
| 830 | 905 | cifs_dbg(VFS, "DFS capability contradicts DFS flag\n"); |
| 831 | 906 | init_copy_chunk_defaults(tcon); |
| 907 | + if (tcon->ses->server->ops->validate_negotiate) | |
| 908 | + rc = tcon->ses->server->ops->validate_negotiate(xid, tcon); | |
| 832 | 909 | tcon_exit: |
| 833 | 910 | free_rsp_buf(resp_buftype, rsp); |
| 834 | 911 | kfree(unc_path); |
| @@ -1214,10 +1291,17 @@ SMB2_ioctl(const unsigned int xid, struct cifs_tcon *tcon, u64 persistent_fid, · 379 unmodified lines | ||
| 1214 | 1291 | rc = SendReceive2(xid, ses, iov, num_iovecs, &resp_buftype, 0); |
| 1215 | 1292 | rsp = (struct smb2_ioctl_rsp *)iov[0].iov_base; |
| 1216 | 1293 | |
| 1217 | − if (rc != 0) { | |
| 1294 | + if ((rc != 0) && (rc != -EINVAL)) { | |
| 1218 | 1295 | if (tcon) |
| 1219 | 1296 | cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE); |
| 1220 | 1297 | goto ioctl_exit; |
| 1298 | + } else if (rc == -EINVAL) { | |
| 1299 | + if ((opcode != FSCTL_SRV_COPYCHUNK_WRITE) && | |
| 1300 | + (opcode != FSCTL_SRV_COPYCHUNK)) { | |
| 1301 | + if (tcon) | |
| 1302 | + cifs_stats_fail_inc(tcon, SMB2_IOCTL_HE); | |
| 1303 | + goto ioctl_exit; | |
| 1304 | + } | |
| 1221 | 1305 | } |
| 1222 | 1306 | |
| 1223 | 1307 | /* 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, · 930 unmodified lines | ||
| 2154 | 2238 | rc = SendReceive2(xid, ses, iov, num, &resp_buftype, 0); |
| 2155 | 2239 | rsp = (struct smb2_set_info_rsp *)iov[0].iov_base; |
| 2156 | 2240 | |
| 2157 | − if (rc != 0) { | |
| 2241 | + if (rc != 0) | |
| 2158 | 2242 | cifs_stats_fail_inc(tcon, SMB2_SET_INFO_HE); |
| 2159 | − goto out; | |
| 2160 | − } | |
| 2161 | −out: | |
| 2243 | + | |
| 2162 | 2244 | free_rsp_buf(resp_buftype, rsp); |
| 2163 | 2245 | kfree(iov); |
| 2164 | 2246 | return rc; |
| @@ -577,13 +577,19 @@ struct copychunk_ioctl_rsp { · 576 unmodified lines | ||
| 577 | 577 | __le32 TotalBytesWritten; |
| 578 | 578 | } __packed; |
| 579 | 579 | |
| 580 | −/* Response and Request are the same format */ | |
| 581 | −struct validate_negotiate_info { | |
| 580 | +struct validate_negotiate_info_req { | |
| 582 | 581 | __le32 Capabilities; |
| 583 | 582 | __u8 Guid[SMB2_CLIENT_GUID_SIZE]; |
| 584 | 583 | __le16 SecurityMode; |
| 585 | 584 | __le16 DialectCount; |
| 586 | − __le16 Dialect[1]; | |
| 585 | + __le16 Dialects[1]; /* dialect (someday maybe list) client asked for */ | |
| 586 | +} __packed; | |
| 587 | + | |
| 588 | +struct validate_negotiate_info_rsp { | |
| 589 | + __le32 Capabilities; | |
| 590 | + __u8 Guid[SMB2_CLIENT_GUID_SIZE]; | |
| 591 | + __le16 SecurityMode; | |
| 592 | + __le16 Dialect; /* Dialect in use for the connection */ | |
| 587 | 593 | } __packed; |
| 588 | 594 | |
| 589 | 595 | #define RSS_CAPABLE 0x00000001 |
| @@ -162,5 +162,6 @@ extern int smb2_lockv(const unsigned int xid, struct cifs_tcon *tcon, · 161 unmodified lines | ||
| 162 | 162 | struct smb2_lock_element *buf); |
| 163 | 163 | extern int SMB2_lease_break(const unsigned int xid, struct cifs_tcon *tcon, |
| 164 | 164 | __u8 *lease_key, const __le32 lease_state); |
| 165 | +extern int smb3_validate_negotiate(const unsigned int, struct cifs_tcon *); | |
| 165 | 166 | |
| 166 | 167 | #endif /* _SMB2PROTO_H */ |
| @@ -90,7 +90,7 @@ · 89 unmodified lines | ||
| 90 | 90 | #define FSCTL_LMR_REQUEST_RESILIENCY 0x001401D4 /* BB add struct */ |
| 91 | 91 | #define FSCTL_LMR_GET_LINK_TRACK_INF 0x001400E8 /* BB add struct */ |
| 92 | 92 | #define FSCTL_LMR_SET_LINK_TRACK_INF 0x001400EC /* BB add struct */ |
| 93 | −#define FSCTL_VALIDATE_NEGOTIATE_INFO 0x00140204 /* BB add struct */ | |
| 93 | +#define FSCTL_VALIDATE_NEGOTIATE_INFO 0x00140204 | |
| 94 | 94 | /* Perform server-side data movement */ |
| 95 | 95 | #define FSCTL_SRV_COPYCHUNK 0x001440F2 |
| 96 | 96 | #define FSCTL_SRV_COPYCHUNK_WRITE 0x001480F2 |