diff options
Diffstat (limited to 'fs/cifs/smb2misc.c')
-rw-r--r-- | fs/cifs/smb2misc.c | 256 |
1 files changed, 246 insertions, 10 deletions
diff --git a/fs/cifs/smb2misc.c b/fs/cifs/smb2misc.c index a4ff5d547554..7b1c5e3287fb 100644 --- a/fs/cifs/smb2misc.c +++ b/fs/cifs/smb2misc.c | |||
@@ -52,7 +52,8 @@ check_smb2_hdr(struct smb2_hdr *hdr, __u64 mid) | |||
52 | cERROR(1, "Bad protocol string signature header %x", | 52 | cERROR(1, "Bad protocol string signature header %x", |
53 | *(unsigned int *) hdr->ProtocolId); | 53 | *(unsigned int *) hdr->ProtocolId); |
54 | if (mid != hdr->MessageId) | 54 | if (mid != hdr->MessageId) |
55 | cERROR(1, "Mids do not match"); | 55 | cERROR(1, "Mids do not match: %llu and %llu", mid, |
56 | hdr->MessageId); | ||
56 | } | 57 | } |
57 | cERROR(1, "Bad SMB detected. The Mid=%llu", hdr->MessageId); | 58 | cERROR(1, "Bad SMB detected. The Mid=%llu", hdr->MessageId); |
58 | return 1; | 59 | return 1; |
@@ -107,7 +108,7 @@ smb2_check_message(char *buf, unsigned int length) | |||
107 | * ie Validate the wct via smb2_struct_sizes table above | 108 | * ie Validate the wct via smb2_struct_sizes table above |
108 | */ | 109 | */ |
109 | 110 | ||
110 | if (length < 2 + sizeof(struct smb2_hdr)) { | 111 | if (length < sizeof(struct smb2_pdu)) { |
111 | if ((length >= sizeof(struct smb2_hdr)) && (hdr->Status != 0)) { | 112 | if ((length >= sizeof(struct smb2_hdr)) && (hdr->Status != 0)) { |
112 | pdu->StructureSize2 = 0; | 113 | pdu->StructureSize2 = 0; |
113 | /* | 114 | /* |
@@ -121,15 +122,15 @@ smb2_check_message(char *buf, unsigned int length) | |||
121 | return 1; | 122 | return 1; |
122 | } | 123 | } |
123 | if (len > CIFSMaxBufSize + MAX_SMB2_HDR_SIZE - 4) { | 124 | if (len > CIFSMaxBufSize + MAX_SMB2_HDR_SIZE - 4) { |
124 | cERROR(1, "SMB length greater than maximum, mid=%lld", mid); | 125 | cERROR(1, "SMB length greater than maximum, mid=%llu", mid); |
125 | return 1; | 126 | return 1; |
126 | } | 127 | } |
127 | 128 | ||
128 | if (check_smb2_hdr(hdr, mid)) | 129 | if (check_smb2_hdr(hdr, mid)) |
129 | return 1; | 130 | return 1; |
130 | 131 | ||
131 | if (hdr->StructureSize != SMB2_HEADER_SIZE) { | 132 | if (hdr->StructureSize != SMB2_HEADER_STRUCTURE_SIZE) { |
132 | cERROR(1, "Illegal structure size %d", | 133 | cERROR(1, "Illegal structure size %u", |
133 | le16_to_cpu(hdr->StructureSize)); | 134 | le16_to_cpu(hdr->StructureSize)); |
134 | return 1; | 135 | return 1; |
135 | } | 136 | } |
@@ -141,12 +142,19 @@ smb2_check_message(char *buf, unsigned int length) | |||
141 | } | 142 | } |
142 | 143 | ||
143 | if (smb2_rsp_struct_sizes[command] != pdu->StructureSize2) { | 144 | if (smb2_rsp_struct_sizes[command] != pdu->StructureSize2) { |
144 | if (hdr->Status == 0 || | 145 | if (command != SMB2_OPLOCK_BREAK_HE && (hdr->Status == 0 || |
145 | pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2) { | 146 | pdu->StructureSize2 != SMB2_ERROR_STRUCTURE_SIZE2)) { |
146 | /* error packets have 9 byte structure size */ | 147 | /* error packets have 9 byte structure size */ |
147 | cERROR(1, "Illegal response size %u for command %d", | 148 | cERROR(1, "Illegal response size %u for command %d", |
148 | le16_to_cpu(pdu->StructureSize2), command); | 149 | le16_to_cpu(pdu->StructureSize2), command); |
149 | return 1; | 150 | return 1; |
151 | } else if (command == SMB2_OPLOCK_BREAK_HE && (hdr->Status == 0) | ||
152 | && (le16_to_cpu(pdu->StructureSize2) != 44) | ||
153 | && (le16_to_cpu(pdu->StructureSize2) != 36)) { | ||
154 | /* special case for SMB2.1 lease break message */ | ||
155 | cERROR(1, "Illegal response size %d for oplock break", | ||
156 | le16_to_cpu(pdu->StructureSize2)); | ||
157 | return 1; | ||
150 | } | 158 | } |
151 | } | 159 | } |
152 | 160 | ||
@@ -161,8 +169,12 @@ smb2_check_message(char *buf, unsigned int length) | |||
161 | if (4 + len != clc_len) { | 169 | if (4 + len != clc_len) { |
162 | cFYI(1, "Calculated size %u length %u mismatch mid %llu", | 170 | cFYI(1, "Calculated size %u length %u mismatch mid %llu", |
163 | clc_len, 4 + len, mid); | 171 | clc_len, 4 + len, mid); |
164 | if (clc_len == 4 + len + 1) /* BB FIXME (fix samba) */ | 172 | /* Windows 7 server returns 24 bytes more */ |
165 | return 0; /* BB workaround Samba 3 bug SessSetup rsp */ | 173 | if (clc_len + 20 == len && command == SMB2_OPLOCK_BREAK_HE) |
174 | return 0; | ||
175 | /* server can return one byte more */ | ||
176 | if (clc_len == 4 + len + 1) | ||
177 | return 0; | ||
166 | return 1; | 178 | return 1; |
167 | } | 179 | } |
168 | return 0; | 180 | return 0; |
@@ -242,7 +254,15 @@ smb2_get_data_area_len(int *off, int *len, struct smb2_hdr *hdr) | |||
242 | ((struct smb2_query_info_rsp *)hdr)->OutputBufferLength); | 254 | ((struct smb2_query_info_rsp *)hdr)->OutputBufferLength); |
243 | break; | 255 | break; |
244 | case SMB2_READ: | 256 | case SMB2_READ: |
257 | *off = ((struct smb2_read_rsp *)hdr)->DataOffset; | ||
258 | *len = le32_to_cpu(((struct smb2_read_rsp *)hdr)->DataLength); | ||
259 | break; | ||
245 | case SMB2_QUERY_DIRECTORY: | 260 | case SMB2_QUERY_DIRECTORY: |
261 | *off = le16_to_cpu( | ||
262 | ((struct smb2_query_directory_rsp *)hdr)->OutputBufferOffset); | ||
263 | *len = le32_to_cpu( | ||
264 | ((struct smb2_query_directory_rsp *)hdr)->OutputBufferLength); | ||
265 | break; | ||
246 | case SMB2_IOCTL: | 266 | case SMB2_IOCTL: |
247 | case SMB2_CHANGE_NOTIFY: | 267 | case SMB2_CHANGE_NOTIFY: |
248 | default: | 268 | default: |
@@ -285,8 +305,9 @@ smb2_get_data_area_len(int *off, int *len, struct smb2_hdr *hdr) | |||
285 | * portion, the number of word parameters and the data portion of the message. | 305 | * portion, the number of word parameters and the data portion of the message. |
286 | */ | 306 | */ |
287 | unsigned int | 307 | unsigned int |
288 | smb2_calc_size(struct smb2_hdr *hdr) | 308 | smb2_calc_size(void *buf) |
289 | { | 309 | { |
310 | struct smb2_hdr *hdr = (struct smb2_hdr *)buf; | ||
290 | struct smb2_pdu *pdu = (struct smb2_pdu *)hdr; | 311 | struct smb2_pdu *pdu = (struct smb2_pdu *)hdr; |
291 | int offset; /* the offset from the beginning of SMB to data area */ | 312 | int offset; /* the offset from the beginning of SMB to data area */ |
292 | int data_length; /* the length of the variable length data area */ | 313 | int data_length; /* the length of the variable length data area */ |
@@ -345,3 +366,218 @@ cifs_convert_path_to_utf16(const char *from, struct cifs_sb_info *cifs_sb) | |||
345 | CIFS_MOUNT_MAP_SPECIAL_CHR); | 366 | CIFS_MOUNT_MAP_SPECIAL_CHR); |
346 | return to; | 367 | return to; |
347 | } | 368 | } |
369 | |||
370 | __le32 | ||
371 | smb2_get_lease_state(struct cifsInodeInfo *cinode) | ||
372 | { | ||
373 | if (cinode->clientCanCacheAll) | ||
374 | return SMB2_LEASE_WRITE_CACHING | SMB2_LEASE_READ_CACHING; | ||
375 | else if (cinode->clientCanCacheRead) | ||
376 | return SMB2_LEASE_READ_CACHING; | ||
377 | return 0; | ||
378 | } | ||
379 | |||
380 | __u8 smb2_map_lease_to_oplock(__le32 lease_state) | ||
381 | { | ||
382 | if (lease_state & SMB2_LEASE_WRITE_CACHING) { | ||
383 | if (lease_state & SMB2_LEASE_HANDLE_CACHING) | ||
384 | return SMB2_OPLOCK_LEVEL_BATCH; | ||
385 | else | ||
386 | return SMB2_OPLOCK_LEVEL_EXCLUSIVE; | ||
387 | } else if (lease_state & SMB2_LEASE_READ_CACHING) | ||
388 | return SMB2_OPLOCK_LEVEL_II; | ||
389 | return 0; | ||
390 | } | ||
391 | |||
392 | struct smb2_lease_break_work { | ||
393 | struct work_struct lease_break; | ||
394 | struct tcon_link *tlink; | ||
395 | __u8 lease_key[16]; | ||
396 | __le32 lease_state; | ||
397 | }; | ||
398 | |||
399 | static void | ||
400 | cifs_ses_oplock_break(struct work_struct *work) | ||
401 | { | ||
402 | struct smb2_lease_break_work *lw = container_of(work, | ||
403 | struct smb2_lease_break_work, lease_break); | ||
404 | int rc; | ||
405 | |||
406 | rc = SMB2_lease_break(0, tlink_tcon(lw->tlink), lw->lease_key, | ||
407 | lw->lease_state); | ||
408 | cFYI(1, "Lease release rc %d", rc); | ||
409 | cifs_put_tlink(lw->tlink); | ||
410 | kfree(lw); | ||
411 | } | ||
412 | |||
413 | static bool | ||
414 | smb2_is_valid_lease_break(char *buffer, struct TCP_Server_Info *server) | ||
415 | { | ||
416 | struct smb2_lease_break *rsp = (struct smb2_lease_break *)buffer; | ||
417 | struct list_head *tmp, *tmp1, *tmp2; | ||
418 | struct cifs_ses *ses; | ||
419 | struct cifs_tcon *tcon; | ||
420 | struct cifsInodeInfo *cinode; | ||
421 | struct cifsFileInfo *cfile; | ||
422 | struct cifs_pending_open *open; | ||
423 | struct smb2_lease_break_work *lw; | ||
424 | bool found; | ||
425 | int ack_req = le32_to_cpu(rsp->Flags & | ||
426 | SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED); | ||
427 | |||
428 | lw = kmalloc(sizeof(struct smb2_lease_break_work), GFP_KERNEL); | ||
429 | if (!lw) { | ||
430 | cERROR(1, "Memory allocation failed during lease break check"); | ||
431 | return false; | ||
432 | } | ||
433 | |||
434 | INIT_WORK(&lw->lease_break, cifs_ses_oplock_break); | ||
435 | lw->lease_state = rsp->NewLeaseState; | ||
436 | |||
437 | cFYI(1, "Checking for lease break"); | ||
438 | |||
439 | /* look up tcon based on tid & uid */ | ||
440 | spin_lock(&cifs_tcp_ses_lock); | ||
441 | list_for_each(tmp, &server->smb_ses_list) { | ||
442 | ses = list_entry(tmp, struct cifs_ses, smb_ses_list); | ||
443 | |||
444 | spin_lock(&cifs_file_list_lock); | ||
445 | list_for_each(tmp1, &ses->tcon_list) { | ||
446 | tcon = list_entry(tmp1, struct cifs_tcon, tcon_list); | ||
447 | |||
448 | cifs_stats_inc(&tcon->stats.cifs_stats.num_oplock_brks); | ||
449 | list_for_each(tmp2, &tcon->openFileList) { | ||
450 | cfile = list_entry(tmp2, struct cifsFileInfo, | ||
451 | tlist); | ||
452 | cinode = CIFS_I(cfile->dentry->d_inode); | ||
453 | |||
454 | if (memcmp(cinode->lease_key, rsp->LeaseKey, | ||
455 | SMB2_LEASE_KEY_SIZE)) | ||
456 | continue; | ||
457 | |||
458 | cFYI(1, "found in the open list"); | ||
459 | cFYI(1, "lease key match, lease break 0x%d", | ||
460 | le32_to_cpu(rsp->NewLeaseState)); | ||
461 | |||
462 | smb2_set_oplock_level(cinode, | ||
463 | smb2_map_lease_to_oplock(rsp->NewLeaseState)); | ||
464 | |||
465 | if (ack_req) | ||
466 | cfile->oplock_break_cancelled = false; | ||
467 | else | ||
468 | cfile->oplock_break_cancelled = true; | ||
469 | |||
470 | queue_work(cifsiod_wq, &cfile->oplock_break); | ||
471 | |||
472 | spin_unlock(&cifs_file_list_lock); | ||
473 | spin_unlock(&cifs_tcp_ses_lock); | ||
474 | return true; | ||
475 | } | ||
476 | |||
477 | found = false; | ||
478 | list_for_each_entry(open, &tcon->pending_opens, olist) { | ||
479 | if (memcmp(open->lease_key, rsp->LeaseKey, | ||
480 | SMB2_LEASE_KEY_SIZE)) | ||
481 | continue; | ||
482 | |||
483 | if (!found && ack_req) { | ||
484 | found = true; | ||
485 | memcpy(lw->lease_key, open->lease_key, | ||
486 | SMB2_LEASE_KEY_SIZE); | ||
487 | lw->tlink = cifs_get_tlink(open->tlink); | ||
488 | queue_work(cifsiod_wq, | ||
489 | &lw->lease_break); | ||
490 | } | ||
491 | |||
492 | cFYI(1, "found in the pending open list"); | ||
493 | cFYI(1, "lease key match, lease break 0x%d", | ||
494 | le32_to_cpu(rsp->NewLeaseState)); | ||
495 | |||
496 | open->oplock = | ||
497 | smb2_map_lease_to_oplock(rsp->NewLeaseState); | ||
498 | } | ||
499 | if (found) { | ||
500 | spin_unlock(&cifs_file_list_lock); | ||
501 | spin_unlock(&cifs_tcp_ses_lock); | ||
502 | return true; | ||
503 | } | ||
504 | } | ||
505 | spin_unlock(&cifs_file_list_lock); | ||
506 | } | ||
507 | spin_unlock(&cifs_tcp_ses_lock); | ||
508 | kfree(lw); | ||
509 | cFYI(1, "Can not process lease break - no lease matched"); | ||
510 | return false; | ||
511 | } | ||
512 | |||
513 | bool | ||
514 | smb2_is_valid_oplock_break(char *buffer, struct TCP_Server_Info *server) | ||
515 | { | ||
516 | struct smb2_oplock_break *rsp = (struct smb2_oplock_break *)buffer; | ||
517 | struct list_head *tmp, *tmp1, *tmp2; | ||
518 | struct cifs_ses *ses; | ||
519 | struct cifs_tcon *tcon; | ||
520 | struct cifsInodeInfo *cinode; | ||
521 | struct cifsFileInfo *cfile; | ||
522 | |||
523 | cFYI(1, "Checking for oplock break"); | ||
524 | |||
525 | if (rsp->hdr.Command != SMB2_OPLOCK_BREAK) | ||
526 | return false; | ||
527 | |||
528 | if (rsp->StructureSize != | ||
529 | smb2_rsp_struct_sizes[SMB2_OPLOCK_BREAK_HE]) { | ||
530 | if (le16_to_cpu(rsp->StructureSize) == 44) | ||
531 | return smb2_is_valid_lease_break(buffer, server); | ||
532 | else | ||
533 | return false; | ||
534 | } | ||
535 | |||
536 | cFYI(1, "oplock level 0x%d", rsp->OplockLevel); | ||
537 | |||
538 | /* look up tcon based on tid & uid */ | ||
539 | spin_lock(&cifs_tcp_ses_lock); | ||
540 | list_for_each(tmp, &server->smb_ses_list) { | ||
541 | ses = list_entry(tmp, struct cifs_ses, smb_ses_list); | ||
542 | list_for_each(tmp1, &ses->tcon_list) { | ||
543 | tcon = list_entry(tmp1, struct cifs_tcon, tcon_list); | ||
544 | |||
545 | cifs_stats_inc(&tcon->stats.cifs_stats.num_oplock_brks); | ||
546 | spin_lock(&cifs_file_list_lock); | ||
547 | list_for_each(tmp2, &tcon->openFileList) { | ||
548 | cfile = list_entry(tmp2, struct cifsFileInfo, | ||
549 | tlist); | ||
550 | if (rsp->PersistentFid != | ||
551 | cfile->fid.persistent_fid || | ||
552 | rsp->VolatileFid != | ||
553 | cfile->fid.volatile_fid) | ||
554 | continue; | ||
555 | |||
556 | cFYI(1, "file id match, oplock break"); | ||
557 | cinode = CIFS_I(cfile->dentry->d_inode); | ||
558 | |||
559 | if (!cinode->clientCanCacheAll && | ||
560 | rsp->OplockLevel == SMB2_OPLOCK_LEVEL_NONE) | ||
561 | cfile->oplock_break_cancelled = true; | ||
562 | else | ||
563 | cfile->oplock_break_cancelled = false; | ||
564 | |||
565 | smb2_set_oplock_level(cinode, | ||
566 | rsp->OplockLevel ? SMB2_OPLOCK_LEVEL_II : 0); | ||
567 | |||
568 | queue_work(cifsiod_wq, &cfile->oplock_break); | ||
569 | |||
570 | spin_unlock(&cifs_file_list_lock); | ||
571 | spin_unlock(&cifs_tcp_ses_lock); | ||
572 | return true; | ||
573 | } | ||
574 | spin_unlock(&cifs_file_list_lock); | ||
575 | spin_unlock(&cifs_tcp_ses_lock); | ||
576 | cFYI(1, "No matching file for oplock break"); | ||
577 | return true; | ||
578 | } | ||
579 | } | ||
580 | spin_unlock(&cifs_tcp_ses_lock); | ||
581 | cFYI(1, "Can not process oplock break for non-existent connection"); | ||
582 | return false; | ||
583 | } | ||