/* Unix SMB/CIFS implementation. SMB2 lock test suite Copyright (C) Stefan Metzmacher 2006 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "includes.h" #include "libcli/smb2/smb2.h" #include "libcli/smb2/smb2_calls.h" #include "../libcli/smb/smbXcli_base.h" #include "torture/torture.h" #include "torture/smb2/proto.h" #include "torture/util.h" #include "lib/events/events.h" #include "param/param.h" #define CHECK_STATUS(status, correct) do { \ const char *_cmt = "(" __location__ ")"; \ torture_assert_ntstatus_equal_goto(torture,status,correct, \ ret,done,_cmt); \ } while (0) #define CHECK_STATUS_CMT(status, correct, cmt) do { \ torture_assert_ntstatus_equal_goto(torture,status,correct, \ ret,done,cmt); \ } while (0) #define CHECK_STATUS_CONT(status, correct) do { \ if (!NT_STATUS_EQUAL(status, correct)) { \ torture_result(torture, TORTURE_FAIL, \ "(%s) Incorrect status %s - should be %s\n", \ __location__, nt_errstr(status), nt_errstr(correct)); \ ret = false; \ }} while (0) #define CHECK_VALUE(v, correct) do { \ const char *_cmt = "(" __location__ ")"; \ torture_assert_int_equal_goto(torture,v,correct,ret,done,_cmt); \ } while (0) #define BASEDIR "testlock" #define TARGET_SUPPORTS_INVALID_LOCK_RANGE(_tctx) \ (torture_setting_bool(_tctx, "invalid_lock_range_support", true)) #define TARGET_IS_W2K8(_tctx) (torture_setting_bool(_tctx, "w2k8", false)) #define WAIT_FOR_ASYNC_RESPONSE(req) \ while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) { \ if (tevent_loop_once(torture->ev) != 0) { \ break; \ } \ } static bool test_valid_request(struct torture_context *torture, struct smb2_tree *tree) { bool ret = true; NTSTATUS status; struct smb2_handle h; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[2]; ZERO_STRUCT(buf); status = torture_smb2_testfile(tree, "lock1.txt", &h); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); lck.in.locks = el; torture_comment(torture, "Test request with 0 locks.\n"); lck.in.lock_count = 0x0000; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 0x0000000000000000; el[0].length = 0x0000000000000000; el[0].reserved = 0x0000000000000000; el[0].flags = 0x00000000; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); lck.in.lock_count = 0x0000; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 0; el[0].length = 0; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_SHARED; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 0; el[0].length = 0; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_NONE; status = smb2_lock(tree, &lck); if (TARGET_IS_W2K8(torture)) { CHECK_STATUS(status, NT_STATUS_OK); torture_warning(torture, "Target has bug validating lock flags " "parameter.\n"); } else { CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); } torture_comment(torture, "Test >63-bit lock requests.\n"); lck.in.file.handle.data[0] +=1; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); lck.in.file.handle.data[0] -=1; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x123ab1; lck.in.file.handle = h; el[0].offset = UINT64_MAX; el[0].length = UINT64_MAX; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); if (TARGET_SUPPORTS_INVALID_LOCK_RANGE(torture)) { CHECK_STATUS(status, NT_STATUS_INVALID_LOCK_RANGE); } else { CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(lck.out.reserved, 0); } lck.in.lock_sequence = 0x123ab2; status = smb2_lock(tree, &lck); if (TARGET_SUPPORTS_INVALID_LOCK_RANGE(torture)) { CHECK_STATUS(status, NT_STATUS_INVALID_LOCK_RANGE); } else { CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); } torture_comment(torture, "Test basic lock stacking.\n"); lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x12345678; lck.in.file.handle = h; el[0].offset = UINT32_MAX; el[0].length = UINT32_MAX; el[0].reserved = 0x87654321; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(lck.out.reserved, 0); el[0].flags = SMB2_LOCK_FLAG_SHARED; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(lck.out.reserved, 0); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(lck.out.reserved, 0); lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x87654321; lck.in.file.handle = h; el[0].offset = 0x00000000FFFFFFFF; el[0].length = 0x00000000FFFFFFFF; el[0].reserved = 0x1234567; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x1234567; lck.in.file.handle = h; el[0].offset = 0x00000000FFFFFFFF; el[0].length = 0x00000000FFFFFFFF; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, "Test flags field permutations.\n"); lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0; lck.in.file.handle = h; el[0].offset = 1; el[0].length = 1; el[0].reserved = 0x00000000; el[0].flags = ~SMB2_LOCK_FLAG_ALL_MASK; status = smb2_lock(tree, &lck); if (TARGET_IS_W2K8(torture)) { CHECK_STATUS(status, NT_STATUS_OK); torture_warning(torture, "Target has bug validating lock flags " "parameter.\n"); } else { CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); } if (TARGET_IS_W2K8(torture)) { el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); } el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); el[0].flags = SMB2_LOCK_FLAG_UNLOCK | SMB2_LOCK_FLAG_EXCLUSIVE; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); el[0].flags = SMB2_LOCK_FLAG_UNLOCK | SMB2_LOCK_FLAG_SHARED; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); el[0].flags = SMB2_LOCK_FLAG_UNLOCK | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); if (TARGET_IS_W2K8(torture)) { CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_warning(torture, "Target has bug validating lock flags " "parameter.\n"); } else { CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); } torture_comment(torture, "Test return error when 2 locks are " "requested\n"); lck.in.lock_count = 2; lck.in.lock_sequence = 0; lck.in.file.handle = h; el[0].offset = 9999; el[0].length = 1; el[0].reserved = 0x00000000; el[1].offset = 9999; el[1].length = 1; el[1].reserved = 0x00000000; lck.in.lock_count = 2; el[0].flags = 0; el[1].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); lck.in.lock_count = 2; el[0].flags = SMB2_LOCK_FLAG_SHARED|SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[1].flags = SMB2_LOCK_FLAG_SHARED; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); lck.in.lock_count = 2; el[0].flags = 0; el[1].flags = 0; status = smb2_lock(tree, &lck); if (TARGET_IS_W2K8(torture)) { CHECK_STATUS(status, NT_STATUS_OK); torture_warning(torture, "Target has bug validating lock flags " "parameter.\n"); } else { CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); } lck.in.lock_count = 2; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; el[1].flags = 0; status = smb2_lock(tree, &lck); if (TARGET_IS_W2K8(torture)) { CHECK_STATUS(status, NT_STATUS_OK); torture_warning(torture, "Target has bug validating lock flags " "parameter.\n"); } else { CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); } lck.in.lock_count = 1; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); lck.in.lock_count = 1; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); lck.in.lock_count = 1; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); lck.in.lock_count = 1; el[0].flags = SMB2_LOCK_FLAG_SHARED; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.lock_count = 2; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; el[1].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.lock_count = 1; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); done: return ret; } struct test_lock_read_write_state { const char *fname; uint32_t lock_flags; NTSTATUS write_h1_status; NTSTATUS read_h1_status; NTSTATUS write_h2_status; NTSTATUS read_h2_status; }; static bool test_lock_read_write(struct torture_context *torture, struct smb2_tree *tree, struct test_lock_read_write_state *s) { bool ret = true; NTSTATUS status; struct smb2_handle h1, h2; uint8_t buf[200]; struct smb2_lock lck; struct smb2_create cr; struct smb2_write wr; struct smb2_read rd; struct smb2_lock_element el[1]; lck.in.locks = el; ZERO_STRUCT(buf); status = torture_smb2_testfile(tree, s->fname, &h1); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_util_write(tree, h1, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h1; el[0].offset = 0; el[0].length = ARRAY_SIZE(buf)/2; el[0].reserved = 0x00000000; el[0].flags = s->lock_flags; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(lck.out.reserved, 0); lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h1; el[0].offset = ARRAY_SIZE(buf)/2; el[0].length = ARRAY_SIZE(buf)/2; el[0].reserved = 0x00000000; el[0].flags = s->lock_flags; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(lck.out.reserved, 0); ZERO_STRUCT(cr); cr.in.oplock_level = 0; cr.in.desired_access = SEC_RIGHTS_FILE_ALL; cr.in.file_attributes = FILE_ATTRIBUTE_NORMAL; cr.in.create_disposition = NTCREATEX_DISP_OPEN_IF; cr.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE| NTCREATEX_SHARE_ACCESS_READ| NTCREATEX_SHARE_ACCESS_WRITE; cr.in.create_options = 0; cr.in.fname = s->fname; status = smb2_create(tree, tree, &cr); CHECK_STATUS(status, NT_STATUS_OK); h2 = cr.out.file.handle; ZERO_STRUCT(wr); wr.in.file.handle = h1; wr.in.offset = ARRAY_SIZE(buf)/2; wr.in.data = data_blob_const(buf, ARRAY_SIZE(buf)/2); status = smb2_write(tree, &wr); CHECK_STATUS(status, s->write_h1_status); ZERO_STRUCT(rd); rd.in.file.handle = h1; rd.in.offset = ARRAY_SIZE(buf)/2; rd.in.length = ARRAY_SIZE(buf)/2; status = smb2_read(tree, tree, &rd); CHECK_STATUS(status, s->read_h1_status); ZERO_STRUCT(wr); wr.in.file.handle = h2; wr.in.offset = ARRAY_SIZE(buf)/2; wr.in.data = data_blob_const(buf, ARRAY_SIZE(buf)/2); status = smb2_write(tree, &wr); CHECK_STATUS(status, s->write_h2_status); ZERO_STRUCT(rd); rd.in.file.handle = h2; rd.in.offset = ARRAY_SIZE(buf)/2; rd.in.length = ARRAY_SIZE(buf)/2; status = smb2_read(tree, tree, &rd); CHECK_STATUS(status, s->read_h2_status); lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h1; el[0].offset = ARRAY_SIZE(buf)/2; el[0].length = ARRAY_SIZE(buf)/2; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(lck.out.reserved, 0); ZERO_STRUCT(wr); wr.in.file.handle = h2; wr.in.offset = ARRAY_SIZE(buf)/2; wr.in.data = data_blob_const(buf, ARRAY_SIZE(buf)/2); status = smb2_write(tree, &wr); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(rd); rd.in.file.handle = h2; rd.in.offset = ARRAY_SIZE(buf)/2; rd.in.length = ARRAY_SIZE(buf)/2; status = smb2_read(tree, tree, &rd); CHECK_STATUS(status, NT_STATUS_OK); done: return ret; } static bool test_lock_rw_none(struct torture_context *torture, struct smb2_tree *tree) { struct test_lock_read_write_state s = { .fname = "lock_rw_none.dat", .lock_flags = SMB2_LOCK_FLAG_NONE, .write_h1_status = NT_STATUS_FILE_LOCK_CONFLICT, .read_h1_status = NT_STATUS_OK, .write_h2_status = NT_STATUS_FILE_LOCK_CONFLICT, .read_h2_status = NT_STATUS_OK, }; if (!TARGET_IS_W2K8(torture)) { torture_skip(torture, "RW-NONE tests the behavior of a " "NONE-type lock, which is the same as a SHARED " "lock but is granted due to a bug in W2K8. If " "target is not W2K8 we skip this test.\n"); } return test_lock_read_write(torture, tree, &s); } static bool test_lock_rw_shared(struct torture_context *torture, struct smb2_tree *tree) { struct test_lock_read_write_state s = { .fname = "lock_rw_shared.dat", .lock_flags = SMB2_LOCK_FLAG_SHARED, .write_h1_status = NT_STATUS_FILE_LOCK_CONFLICT, .read_h1_status = NT_STATUS_OK, .write_h2_status = NT_STATUS_FILE_LOCK_CONFLICT, .read_h2_status = NT_STATUS_OK, }; return test_lock_read_write(torture, tree, &s); } static bool test_lock_rw_exclusive(struct torture_context *torture, struct smb2_tree *tree) { struct test_lock_read_write_state s = { .fname = "lock_rw_exclusive.dat", .lock_flags = SMB2_LOCK_FLAG_EXCLUSIVE, .write_h1_status = NT_STATUS_OK, .read_h1_status = NT_STATUS_OK, .write_h2_status = NT_STATUS_FILE_LOCK_CONFLICT, .read_h2_status = NT_STATUS_FILE_LOCK_CONFLICT, }; return test_lock_read_write(torture, tree, &s); } static bool test_lock_auto_unlock(struct torture_context *torture, struct smb2_tree *tree) { bool ret = true; NTSTATUS status; struct smb2_handle h; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[1]; ZERO_STRUCT(buf); status = torture_smb2_testfile(tree, "autounlock.txt", &h); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(lck); ZERO_STRUCT(el[0]); lck.in.locks = el; lck.in.lock_count = 0x0001; lck.in.file.handle = h; el[0].offset = 0; el[0].length = 1; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); status = smb2_lock(tree, &lck); if (TARGET_IS_W2K8(torture)) { CHECK_STATUS(status, NT_STATUS_OK); torture_warning(torture, "Target has \"pretty please\" bug. " "A contending lock request on the same handle " "unlocks the lock.\n"); } else { CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); } status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); done: return ret; } /* test different lock ranges and see if different handles conflict */ static bool test_lock(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[2]; const char *fname = BASEDIR "\\async.txt"; status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); lck.in.locks = el; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; torture_comment(torture, "Trying 0/0 lock\n"); el[0].offset = 0x0000000000000000; el[0].length = 0x0000000000000000; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, "Trying 0/1 lock\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[0].offset = 0x0000000000000000; el[0].length = 0x0000000000000001; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, "Trying 0xEEFFFFF lock\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[0].offset = 0xEEFFFFFF; el[0].length = 4000; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, "Trying 0xEF00000 lock\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[0].offset = 0xEF000000; el[0].length = 4000; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, "Trying (2^63 - 1)/1\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[0].offset = 1; el[0].offset <<= 63; el[0].offset--; el[0].length = 1; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, "Trying 2^63/1\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[0].offset = 1; el[0].offset <<= 63; el[0].length = 1; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, "Trying max/0 lock\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[0].offset = ~0; el[0].length = 0; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, "Trying max/1 lock\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[0].offset = ~0; el[0].length = 1; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, "Trying max/2 lock\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[0].offset = ~0; el[0].length = 2; status = smb2_lock(tree, &lck); if (TARGET_SUPPORTS_INVALID_LOCK_RANGE(torture)) { CHECK_STATUS(status, NT_STATUS_INVALID_LOCK_RANGE); } else { CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); } torture_comment(torture, "Trying wrong handle unlock\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[0].offset = 10001; el[0].length = 40002; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h2; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); lck.in.file.handle = h; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); done: smb2_util_close(tree, h2); smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } /* test SMB2 LOCK async operation */ static bool test_async(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[2]; struct smb2_request *req = NULL; const char *fname = BASEDIR "\\async.txt"; status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); lck.in.locks = el; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 100; el[0].length = 50; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; torture_comment(torture, " Acquire first lock\n"); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, " Second lock should pend on first\n"); lck.in.file.handle = h2; req = smb2_lock_send(tree, &lck); WAIT_FOR_ASYNC_RESPONSE(req); torture_comment(torture, " Unlock first lock\n"); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, " Second lock should now succeed\n"); lck.in.file.handle = h2; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; status = smb2_lock_recv(req, &lck); CHECK_STATUS(status, NT_STATUS_OK); done: smb2_util_close(tree, h2); smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } /* test SMB2 LOCK Cancel operation */ static bool test_cancel(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[2]; struct smb2_request *req = NULL; const char *fname = BASEDIR "\\cancel.txt"; status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); lck.in.locks = el; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 100; el[0].length = 10; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; torture_comment(torture, "Testing basic cancel\n"); torture_comment(torture, " Acquire first lock\n"); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, " Second lock should pend on first\n"); lck.in.file.handle = h2; req = smb2_lock_send(tree, &lck); WAIT_FOR_ASYNC_RESPONSE(req); torture_comment(torture, " Cancel the second lock\n"); smb2_cancel(req); lck.in.file.handle = h2; status = smb2_lock_recv(req, &lck); CHECK_STATUS(status, NT_STATUS_CANCELLED); torture_comment(torture, " Unlock first lock\n"); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, "Testing cancel by unlock\n"); torture_comment(torture, " Acquire first lock\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, " Second lock should pend on first\n"); lck.in.file.handle = h2; req = smb2_lock_send(tree, &lck); WAIT_FOR_ASYNC_RESPONSE(req); torture_comment(torture, " Attempt to unlock pending second lock\n"); lck.in.file.handle = h2; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, " Now cancel the second lock\n"); smb2_cancel(req); lck.in.file.handle = h2; status = smb2_lock_recv(req, &lck); CHECK_STATUS(status, NT_STATUS_CANCELLED); torture_comment(torture, " Unlock first lock\n"); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, "Testing cancel by close\n"); torture_comment(torture, " Acquire first lock\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, " Second lock should pend on first\n"); lck.in.file.handle = h2; req = smb2_lock_send(tree, &lck); WAIT_FOR_ASYNC_RESPONSE(req); torture_comment(torture, " Close the second lock handle\n"); smb2_util_close(tree, h2); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, " Check pending lock reply\n"); status = smb2_lock_recv(req, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, " Unlock first lock\n"); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); done: smb2_util_close(tree, h2); smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } /* test SMB2 LOCK Cancel by tree disconnect */ static bool test_cancel_tdis(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[2]; struct smb2_request *req = NULL; const char *fname = BASEDIR "\\cancel_tdis.txt"; status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); lck.in.locks = el; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 100; el[0].length = 10; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; torture_comment(torture, "Testing cancel by tree disconnect\n"); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, " Acquire first lock\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, " Second lock should pend on first\n"); lck.in.file.handle = h2; req = smb2_lock_send(tree, &lck); WAIT_FOR_ASYNC_RESPONSE(req); torture_comment(torture, " Disconnect the tree\n"); smb2_tdis(tree); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, " Check pending lock reply\n"); status = smb2_lock_recv(req, &lck); if (!NT_STATUS_EQUAL(status, NT_STATUS_RANGE_NOT_LOCKED)) { /* * The status depends on the server internals * the order in which the files are closed * by smb2_tdis(). */ CHECK_STATUS(status, NT_STATUS_OK); } torture_comment(torture, " Attempt to unlock first lock\n"); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); /* * Most Windows versions have a strange order to * verify the session id, tree id and file id. * (They should be checked in that order, but windows * seems to check the file id before the others). */ if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_NAME_DELETED)) { CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); } done: smb2_util_close(tree, h2); smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } /* test SMB2 LOCK Cancel by user logoff */ static bool test_cancel_logoff(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[2]; struct smb2_request *req = NULL; const char *fname = BASEDIR "\\cancel_logoff.txt"; status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); lck.in.locks = el; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 100; el[0].length = 10; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; torture_comment(torture, "Testing cancel by ulogoff\n"); torture_comment(torture, " Acquire first lock\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, " Second lock should pend on first\n"); lck.in.file.handle = h2; req = smb2_lock_send(tree, &lck); WAIT_FOR_ASYNC_RESPONSE(req); torture_comment(torture, " Logoff user\n"); smb2_logoff(tree->session); torture_comment(torture, " Check pending lock reply\n"); status = smb2_lock_recv(req, &lck); if (!NT_STATUS_EQUAL(status, NT_STATUS_RANGE_NOT_LOCKED)) { /* * The status depends on the server internals * the order in which the files are closed * by smb2_logoff(). */ CHECK_STATUS(status, NT_STATUS_OK); } torture_comment(torture, " Attempt to unlock first lock\n"); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); /* * Most Windows versions have a strange order to * verify the session id, tree id and file id. * (They should be checked in that order, but windows * seems to check the file id before the others). */ if (!NT_STATUS_EQUAL(status, NT_STATUS_USER_SESSION_DELETED)) { CHECK_STATUS(status, NT_STATUS_FILE_CLOSED); } done: smb2_util_close(tree, h2); smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } /* * Test NT_STATUS_LOCK_NOT_GRANTED vs. NT_STATUS_FILE_LOCK_CONFLICT * * The SMBv1 protocol returns a different error code on lock acquisition * failure depending on a number of parameters, including what error code * was returned to the previous failure. * * SMBv2 has cleaned up these semantics and should always return * NT_STATUS_LOCK_NOT_GRANTED to failed lock requests, and * NT_STATUS_FILE_LOCK_CONFLICT to failed read/write requests due to a lock * being held on that range. */ static bool test_errorcode(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[2]; const char *fname = BASEDIR "\\errorcode.txt"; status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); lck.in.locks = el; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 100; el[0].length = 10; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; torture_comment(torture, "Testing LOCK_NOT_GRANTED vs. " "FILE_LOCK_CONFLICT\n"); if (TARGET_IS_W2K8(torture)) { torture_result(torture, TORTURE_SKIP, "Target has \"pretty please\" bug. A contending lock " "request on the same handle unlocks the lock."); goto done; } status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Demonstrate that the first conflicting lock on each handle gives * LOCK_NOT_GRANTED. */ lck.in.file.handle = h; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* Demonstrate that each following conflict also gives * LOCK_NOT_GRANTED */ lck.in.file.handle = h; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* Demonstrate that the smbpid doesn't matter */ lck.in.file.handle = h; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* Demonstrate that a 0-byte lock inside the locked range still * gives the same error. */ el[0].offset = 102; el[0].length = 0; lck.in.file.handle = h; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* Demonstrate that a lock inside the locked range still gives the * same error. */ el[0].offset = 102; el[0].length = 5; lck.in.file.handle = h; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.file.handle = h2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); done: smb2_util_close(tree, h2); smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } /** * Tests zero byte locks. */ struct double_lock_test { struct smb2_lock_element lock1; struct smb2_lock_element lock2; NTSTATUS status; }; static struct double_lock_test zero_byte_tests[] = { /* {offset, count, reserved, flags}, * {offset, count, reserved, flags}, * status */ /** First, takes a zero byte lock at offset 10. Then: * - Taking 0 byte lock at 10 should succeed. * - Taking 1 byte locks at 9,10,11 should succeed. * - Taking 2 byte lock at 9 should fail. * - Taking 2 byte lock at 10 should succeed. * - Taking 3 byte lock at 9 should fail. */ {{10, 0, 0, 0}, {10, 0, 0, 0}, NT_STATUS_OK}, {{10, 0, 0, 0}, {9, 1, 0, 0}, NT_STATUS_OK}, {{10, 0, 0, 0}, {10, 1, 0, 0}, NT_STATUS_OK}, {{10, 0, 0, 0}, {11, 1, 0, 0}, NT_STATUS_OK}, {{10, 0, 0, 0}, {9, 2, 0, 0}, NT_STATUS_LOCK_NOT_GRANTED}, {{10, 0, 0, 0}, {10, 2, 0, 0}, NT_STATUS_OK}, {{10, 0, 0, 0}, {9, 3, 0, 0}, NT_STATUS_LOCK_NOT_GRANTED}, /** Same, but opposite order. */ {{10, 0, 0, 0}, {10, 0, 0, 0}, NT_STATUS_OK}, {{9, 1, 0, 0}, {10, 0, 0, 0}, NT_STATUS_OK}, {{10, 1, 0, 0}, {10, 0, 0, 0}, NT_STATUS_OK}, {{11, 1, 0, 0}, {10, 0, 0, 0}, NT_STATUS_OK}, {{9, 2, 0, 0}, {10, 0, 0, 0}, NT_STATUS_LOCK_NOT_GRANTED}, {{10, 2, 0, 0}, {10, 0, 0, 0}, NT_STATUS_OK}, {{9, 3, 0, 0}, {10, 0, 0, 0}, NT_STATUS_LOCK_NOT_GRANTED}, /** Zero zero case. */ {{0, 0, 0, 0}, {0, 0, 0, 0}, NT_STATUS_OK}, }; static bool test_zerobytelength(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; int i; const char *fname = BASEDIR "\\zero.txt"; torture_comment(torture, "Testing zero length byte range locks:\n"); status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); /* Setup initial parameters */ lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; /* Try every combination of locks in zero_byte_tests, using the same * handle for both locks. The first lock is assumed to succeed. The * second lock may contend, depending on the expected status. */ for (i = 0; i < ARRAY_SIZE(zero_byte_tests); i++) { torture_comment(torture, " ... {%llu, %llu} + {%llu, %llu} = %s\n", (unsigned long long) zero_byte_tests[i].lock1.offset, (unsigned long long) zero_byte_tests[i].lock1.length, (unsigned long long) zero_byte_tests[i].lock2.offset, (unsigned long long) zero_byte_tests[i].lock2.length, nt_errstr(zero_byte_tests[i].status)); /* Lock both locks. */ lck.in.locks = &zero_byte_tests[i].lock1; lck.in.locks[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.locks = &zero_byte_tests[i].lock2; lck.in.locks[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS_CONT(status, zero_byte_tests[i].status); /* Unlock both locks in reverse order. */ lck.in.locks[0].flags = SMB2_LOCK_FLAG_UNLOCK; if (NT_STATUS_EQUAL(status, NT_STATUS_OK)) { status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); } lck.in.locks = &zero_byte_tests[i].lock1; lck.in.locks[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); } /* Try every combination of locks in zero_byte_tests, using two * different handles. */ for (i = 0; i < ARRAY_SIZE(zero_byte_tests); i++) { torture_comment(torture, " ... {%llu, %llu} + {%llu, %llu} = %s\n", (unsigned long long) zero_byte_tests[i].lock1.offset, (unsigned long long) zero_byte_tests[i].lock1.length, (unsigned long long) zero_byte_tests[i].lock2.offset, (unsigned long long) zero_byte_tests[i].lock2.length, nt_errstr(zero_byte_tests[i].status)); /* Lock both locks. */ lck.in.file.handle = h; lck.in.locks = &zero_byte_tests[i].lock1; lck.in.locks[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h2; lck.in.locks = &zero_byte_tests[i].lock2; lck.in.locks[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS_CONT(status, zero_byte_tests[i].status); /* Unlock both locks in reverse order. */ lck.in.file.handle = h2; lck.in.locks[0].flags = SMB2_LOCK_FLAG_UNLOCK; if (NT_STATUS_EQUAL(status, NT_STATUS_OK)) { status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); } lck.in.file.handle = h; lck.in.locks = &zero_byte_tests[i].lock1; lck.in.locks[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); } done: smb2_util_close(tree, h2); smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } static bool test_zerobyteread(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[1]; struct smb2_read rd; const char *fname = BASEDIR "\\zerobyteread.txt"; status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); /* Setup initial parameters */ lck.in.locks = el; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; ZERO_STRUCT(rd); rd.in.file.handle = h2; torture_comment(torture, "Testing zero byte read on lock range:\n"); /* Take an exclusive lock */ torture_comment(torture, " taking exclusive lock.\n"); el[0].offset = 0; el[0].length = 10; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(lck.out.reserved, 0); /* Try a zero byte read */ torture_comment(torture, " reading 0 bytes.\n"); rd.in.offset = 5; rd.in.length = 0; status = smb2_read(tree, tree, &rd); torture_assert_int_equal_goto(torture, rd.out.data.length, 0, ret, done, "zero byte read did not return 0 bytes"); CHECK_STATUS(status, NT_STATUS_OK); /* Unlock lock */ el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(lck.out.reserved, 0); torture_comment(torture, "Testing zero byte read on zero byte lock " "range:\n"); /* Take an exclusive lock */ torture_comment(torture, " taking exclusive 0-byte lock.\n"); el[0].offset = 5; el[0].length = 0; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(lck.out.reserved, 0); /* Try a zero byte read before the lock */ torture_comment(torture, " reading 0 bytes before the lock.\n"); rd.in.offset = 4; rd.in.length = 0; status = smb2_read(tree, tree, &rd); torture_assert_int_equal_goto(torture, rd.out.data.length, 0, ret, done, "zero byte read did not return 0 bytes"); CHECK_STATUS(status, NT_STATUS_OK); /* Try a zero byte read on the lock */ torture_comment(torture, " reading 0 bytes on the lock.\n"); rd.in.offset = 5; rd.in.length = 0; status = smb2_read(tree, tree, &rd); torture_assert_int_equal_goto(torture, rd.out.data.length, 0, ret, done, "zero byte read did not return 0 bytes"); CHECK_STATUS(status, NT_STATUS_OK); /* Try a zero byte read after the lock */ torture_comment(torture, " reading 0 bytes after the lock.\n"); rd.in.offset = 6; rd.in.length = 0; status = smb2_read(tree, tree, &rd); torture_assert_int_equal_goto(torture, rd.out.data.length, 0, ret, done, "zero byte read did not return 0 bytes"); CHECK_STATUS(status, NT_STATUS_OK); /* Unlock lock */ el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); CHECK_VALUE(lck.out.reserved, 0); done: smb2_util_close(tree, h2); smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } static bool test_unlock(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el1[1]; struct smb2_lock_element el2[1]; const char *fname = BASEDIR "\\unlock.txt"; status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); /* Setup initial parameters */ lck.in.locks = el1; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; el1[0].offset = 0; el1[0].length = 10; el1[0].reserved = 0x00000000; /* Take exclusive lock, then unlock it with a shared-unlock call. */ torture_comment(torture, "Testing unlock exclusive with shared\n"); torture_comment(torture, " taking exclusive lock.\n"); lck.in.file.handle = h; el1[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, " try to unlock the exclusive with a shared " "unlock call.\n"); el1[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); torture_comment(torture, " try shared lock on h2, to test the " "unlock.\n"); lck.in.file.handle = h2; el1[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); torture_comment(torture, " unlock the exclusive lock\n"); lck.in.file.handle = h; el1[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Unlock a shared lock with an exclusive-unlock call. */ torture_comment(torture, "Testing unlock shared with exclusive\n"); torture_comment(torture, " taking shared lock.\n"); lck.in.file.handle = h; el1[0].flags = SMB2_LOCK_FLAG_SHARED; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, " try to unlock the shared with an exclusive " "unlock call.\n"); el1[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); torture_comment(torture, " try exclusive lock on h2, to test the " "unlock.\n"); lck.in.file.handle = h2; el1[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); torture_comment(torture, " unlock the exclusive lock\n"); lck.in.file.handle = h; el1[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Test unlocking of stacked 0-byte locks. SMB2 0-byte lock behavior * should be the same as >0-byte behavior. Exclusive locks should be * unlocked before shared. */ torture_comment(torture, "Test unlocking stacked 0-byte locks\n"); lck.in.locks = el1; lck.in.file.handle = h; el1[0].offset = 10; el1[0].length = 0; el1[0].reserved = 0x00000000; el2[0].offset = 5; el2[0].length = 10; el2[0].reserved = 0x00000000; /* lock 0-byte exclusive */ el1[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* lock 0-byte shared */ el1[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* test contention */ lck.in.locks = el2; lck.in.file.handle = h2; el2[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.locks = el2; lck.in.file.handle = h2; el2[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* unlock */ lck.in.locks = el1; lck.in.file.handle = h; el1[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* test - can we take a shared lock? */ lck.in.locks = el2; lck.in.file.handle = h2; el2[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el2[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* cleanup */ lck.in.locks = el1; lck.in.file.handle = h; el1[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Test unlocking of stacked exclusive, shared locks. Exclusive * should be unlocked before any shared. */ torture_comment(torture, "Test unlocking stacked exclusive/shared " "locks\n"); lck.in.locks = el1; lck.in.file.handle = h; el1[0].offset = 10; el1[0].length = 10; el1[0].reserved = 0x00000000; el2[0].offset = 5; el2[0].length = 10; el2[0].reserved = 0x00000000; /* lock exclusive */ el1[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* lock shared */ el1[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* test contention */ lck.in.locks = el2; lck.in.file.handle = h2; el2[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.locks = el2; lck.in.file.handle = h2; el2[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* unlock */ lck.in.locks = el1; lck.in.file.handle = h; el1[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* test - can we take a shared lock? */ lck.in.locks = el2; lck.in.file.handle = h2; el2[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el2[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* cleanup */ lck.in.locks = el1; lck.in.file.handle = h; el1[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); done: smb2_util_close(tree, h2); smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } static bool test_multiple_unlock(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[2]; const char *fname = BASEDIR "\\unlock_multiple.txt"; status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, "Testing multiple unlocks:\n"); /* Setup initial parameters */ lck.in.lock_count = 0x0002; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 0; el[0].length = 10; el[0].reserved = 0x00000000; el[1].offset = 10; el[1].length = 10; el[1].reserved = 0x00000000; /* Test1: Acquire second lock, but not first. */ torture_comment(torture, " unlock 2 locks, first one not locked. " "Expect no locks unlocked. \n"); lck.in.lock_count = 0x0001; el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = &el[1]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Try to unlock both locks */ lck.in.lock_count = 0x0002; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; el[1].flags = SMB2_LOCK_FLAG_UNLOCK; lck.in.locks = el; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); /* Second lock should not be unlocked. */ lck.in.lock_count = 0x0001; el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = &el[1]; status = smb2_lock(tree, &lck); if (TARGET_IS_W2K8(torture)) { CHECK_STATUS(status, NT_STATUS_OK); torture_warning(torture, "Target has \"pretty please\" bug. " "A contending lock request on the same handle " "unlocks the lock.\n"); } else { CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); } /* cleanup */ lck.in.lock_count = 0x0001; el[1].flags = SMB2_LOCK_FLAG_UNLOCK; lck.in.locks = &el[1]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Test2: Acquire first lock, but not second. */ torture_comment(torture, " unlock 2 locks, second one not locked. " "Expect first lock unlocked.\n"); lck.in.lock_count = 0x0001; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = &el[0]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Try to unlock both locks */ lck.in.lock_count = 0x0002; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; el[1].flags = SMB2_LOCK_FLAG_UNLOCK; lck.in.locks = el; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); /* First lock should be unlocked. */ lck.in.lock_count = 0x0001; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = &el[0]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* cleanup */ lck.in.lock_count = 0x0001; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; lck.in.locks = &el[0]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Test3: Request 2 locks, second will contend. What happens to the * first? */ torture_comment(torture, " request 2 locks, second one will contend. " "Expect both to fail.\n"); /* Lock the second range */ lck.in.lock_count = 0x0001; el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = &el[1]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Request both locks */ lck.in.lock_count = 0x0002; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = el; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* First lock should be unlocked. */ lck.in.lock_count = 0x0001; lck.in.locks = &el[0]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* cleanup */ if (TARGET_IS_W2K8(torture)) { lck.in.lock_count = 0x0001; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; lck.in.locks = &el[0]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_warning(torture, "Target has \"pretty please\" bug. " "A contending lock request on the same handle " "unlocks the lock.\n"); } else { lck.in.lock_count = 0x0002; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; el[1].flags = SMB2_LOCK_FLAG_UNLOCK; lck.in.locks = el; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); } /* Test4: Request unlock and lock. The lock contends, is the unlock * then relocked? SMB2 doesn't like the lock and unlock requests in the * same packet. The unlock will succeed, but the lock will return * INVALID_PARAMETER. This behavior is described in MS-SMB2 * 3.3.5.14.1 */ torture_comment(torture, " request unlock and lock, second one will " "error. Expect the unlock to succeed.\n"); /* Lock both ranges */ lck.in.lock_count = 0x0002; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = el; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Attempt to unlock the first range and lock the second. The lock * request will error. */ lck.in.lock_count = 0x0002; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = el; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); /* The first lock should've been unlocked */ lck.in.lock_count = 0x0001; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = &el[0]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* cleanup */ lck.in.lock_count = 0x0002; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; el[1].flags = SMB2_LOCK_FLAG_UNLOCK; lck.in.locks = el; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Test10: SMB2 only test. Request unlock and lock in same packet. * Neither contend. SMB2 doesn't like lock and unlock requests in the * same packet. The unlock will succeed, but the lock will return * INVALID_PARAMETER. */ torture_comment(torture, " request unlock and lock. Unlock will " "succeed, but lock will fail.\n"); /* Lock first range */ lck.in.lock_count = 0x0001; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = &el[0]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Attempt to unlock the first range and lock the second */ lck.in.lock_count = 0x0002; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = el; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); /* Neither lock should still be locked */ lck.in.lock_count = 0x0002; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = el; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* cleanup */ lck.in.lock_count = 0x0002; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; el[1].flags = SMB2_LOCK_FLAG_UNLOCK; lck.in.locks = el; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Test11: SMB2 only test. Request lock and unlock in same packet. * Neither contend. SMB2 doesn't like lock and unlock requests in the * same packet. The lock will succeed, the unlock will fail with * INVALID_PARAMETER, and the lock will be unlocked before return. */ torture_comment(torture, " request lock and unlock. Both will " "fail.\n"); /* Lock second range */ lck.in.lock_count = 0x0001; el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = &el[1]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Attempt to lock the first range and unlock the second */ lck.in.lock_count = 0x0002; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; el[1].flags = SMB2_LOCK_FLAG_UNLOCK; lck.in.locks = el; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER); /* First range should be unlocked, second locked. */ lck.in.lock_count = 0x0001; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = &el[0]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.lock_count = 0x0001; el[1].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; lck.in.locks = &el[1]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* cleanup */ if (TARGET_IS_W2K8(torture)) { lck.in.lock_count = 0x0001; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; lck.in.locks = &el[0]; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_warning(torture, "Target has \"pretty please\" bug. " "A contending lock request on the same handle " "unlocks the lock.\n"); } else { lck.in.lock_count = 0x0002; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; el[1].flags = SMB2_LOCK_FLAG_UNLOCK; lck.in.locks = el; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); } done: smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } /** * Test lock stacking * - some tests ported from BASE-LOCK-LOCK5 */ static bool test_stacking(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[1]; const char *fname = BASEDIR "\\stacking.txt"; status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, "Testing lock stacking:\n"); /* Setup initial parameters */ lck.in.locks = el; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 0; el[0].length = 10; el[0].reserved = 0x00000000; /* Try to take a shared lock, then a shared lock on same handle */ torture_comment(torture, " stacking a shared on top of a shared" "lock succeeds.\n"); el[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* cleanup */ el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Try to take an exclusive lock, then a shared lock on same handle */ torture_comment(torture, " stacking a shared on top of an exclusive " "lock succeeds.\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* stacking a shared from a different handle should fail */ lck.in.file.handle = h2; el[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* cleanup */ lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* ensure the 4th unlock fails */ el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); /* ensure a second handle can now take an exclusive lock */ lck.in.file.handle = h2; el[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Try to take an exclusive lock, then a shared lock on a * different handle */ torture_comment(torture, " stacking a shared on top of an exclusive " "lock with different handles fails.\n"); lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h2; el[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* cleanup */ lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* Try to take a shared lock, then stack an exclusive with same * handle. */ torture_comment(torture, " stacking an exclusive on top of a shared " "lock fails.\n"); el[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* cleanup */ el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); if (TARGET_IS_W2K8(torture)) { CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_warning(torture, "Target has \"pretty please\" bug. " "A contending lock request on the same handle " "unlocks the lock.\n"); } else { CHECK_STATUS(status, NT_STATUS_OK); } /* Prove that two exclusive locks do not stack on the same handle. */ torture_comment(torture, " two exclusive locks do not stack.\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* cleanup */ el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); if (TARGET_IS_W2K8(torture)) { CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_warning(torture, "Target has \"pretty please\" bug. " "A contending lock request on the same handle " "unlocks the lock.\n"); } else { CHECK_STATUS(status, NT_STATUS_OK); } done: smb2_util_close(tree, h2); smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } /** * Test lock contention * - shared lock should contend with exclusive lock on different handle */ static bool test_contend(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[1]; const char *fname = BASEDIR "\\contend.txt"; status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, "Testing lock contention:\n"); /* Setup initial parameters */ lck.in.locks = el; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 0; el[0].length = 10; el[0].reserved = 0x00000000; /* Take an exclusive lock, then a shared lock on different handle */ torture_comment(torture, " shared should contend on exclusive on " "different handle.\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); lck.in.file.handle = h2; el[0].flags = SMB2_LOCK_FLAG_SHARED | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* cleanup */ lck.in.file.handle = h; el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); done: smb2_util_close(tree, h2); smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } /** * Test locker context * - test that pid does not affect the locker context */ static bool test_context(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[1]; const char *fname = BASEDIR "\\context.txt"; status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, "Testing locker context:\n"); /* Setup initial parameters */ lck.in.locks = el; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 0; el[0].length = 10; el[0].reserved = 0x00000000; /* Take an exclusive lock, then try to unlock with a different pid, * same handle. This shows that the pid doesn't affect the locker * context in SMB2. */ torture_comment(torture, " pid shouldn't affect locker context\n"); el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el[0].flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); done: smb2_util_close(tree, h2); smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } /** * Test as much of the potential lock range as possible * - test ported from BASE-LOCK-LOCK3 */ static bool test_range(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h = {{0}}; struct smb2_handle h2 = {{0}}; uint8_t buf[200]; struct smb2_lock lck; struct smb2_lock_element el[1]; uint64_t offset, i; extern int torture_numops; const char *fname = BASEDIR "\\range.txt"; #define NEXT_OFFSET offset += (~(uint64_t)0) / torture_numops status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); ZERO_STRUCT(buf); status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf)); CHECK_STATUS(status, NT_STATUS_OK); status = torture_smb2_testfile(tree, fname, &h2); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, "Testing locks spread across the 64-bit " "offset range\n"); if (TARGET_IS_W2K8(torture)) { torture_result(torture, TORTURE_SKIP, "Target has \"pretty please\" bug. A contending lock " "request on the same handle unlocks the lock."); goto done; } /* Setup initial parameters */ lck.in.locks = el; lck.in.lock_count = 0x0001; lck.in.lock_sequence = 0x00000000; lck.in.file.handle = h; el[0].offset = 0; el[0].length = 1; el[0].reserved = 0x00000000; el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; torture_comment(torture, " establishing %d locks\n", torture_numops); for (offset=i=0; i * perform lock sequence verification ... * * ... * * <314> Section 3.3.5.14: Windows 7 and Windows Server 2008 R2 perform * lock sequence verification only when Open.IsResilient is TRUE. * Windows 8 through Windows 10 v1909 and Windows Server 2012 through * Windows Server v1909 perform lock sequence verification only when * Open.IsResilient or Open.IsPersistent is TRUE. * * Note <314> also applies to all versions (at least) up to Windows Server v2004. * * Hopefully this will be fixed in future Windows versions and they * will avoid Note <314>. */ static bool test_replay_broken_windows(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle h; struct smb2_ioctl ioctl; struct smb2_lock lck; struct smb2_lock_element el; uint8_t res_req[8]; const char *fname = BASEDIR "\\replay_broken_windows.txt"; struct smb2_transport *transport = tree->session->transport; if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB2_10) { torture_skip(torture, "SMB 2.1.0 Dialect family or above \ required for Lock Replay tests\n"); } status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); torture_comment(torture, "Testing Open File:\n"); status = torture_smb2_testfile(tree, fname, &h); CHECK_STATUS(status, NT_STATUS_OK); /* * Setup initial parameters */ el = (struct smb2_lock_element) { .length = 100, .offset = 100, }; lck = (struct smb2_lock) { .in.locks = &el, .in.lock_count = 0x0001, .in.file.handle = h }; torture_comment(torture, "Testing Lock Replay detection [ignored]:\n"); lck.in.lock_sequence = 0x010 + 0x1; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); if (NT_STATUS_IS_OK(status)) { lck.in.lock_sequence = 0x020 + 0x2; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); lck.in.lock_sequence = 0x010 + 0x1; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); if (smbXcli_conn_protocol(transport->conn) > PROTOCOL_SMB2_10) { torture_skip_goto(torture, done, "SMB3 Server implements LockSequence " "for all handles\n"); } } CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); if (smbXcli_conn_protocol(transport->conn) > PROTOCOL_SMB2_10) { torture_comment(torture, "\nSMB3 Server implements LockSequence as SMB 2.1.0" " LEGACY BROKEN Windows!!!\n\n"); } torture_comment(torture, "Testing SMB 2.1.0 LockSequence for ResilientHandles\n"); el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, "Testing Set Resiliency:\n"); SIVAL(res_req, 0, 1000); /* timeout */ SIVAL(res_req, 4, 0); /* reserved */ ioctl = (struct smb2_ioctl) { .level = RAW_IOCTL_SMB2, .in.file.handle = h, .in.function = FSCTL_LMR_REQ_RESILIENCY, .in.max_output_response = 0, .in.flags = SMB2_IOCTL_FLAG_IS_FSCTL, .in.out.data = res_req, .in.out.length = sizeof(res_req) }; status = smb2_ioctl(tree, torture, &ioctl); CHECK_STATUS(status, NT_STATUS_OK); /* * Test with an invalid bucket number (only 1..64 are valid). * With an invalid number, lock replay detection is not performed. */ torture_comment(torture, "Testing Lock (ignored) Replay detection " "(Bucket No: 0 (invalid)) [ignored]:\n"); lck.in.lock_sequence = 0x000 + 0x1; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, "Testing Lock Replay detection " "(Bucket No: 1):\n"); /* * Obtain Exclusive Lock of length 100 bytes using Bucket Num 1 * and Bucket Seq 1. */ lck.in.lock_sequence = 0x010 + 0x1; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* * Server detects Replay of Byte Range locks using the Lock Sequence * Numbers. And ignores the requests completely. */ status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* status: still locked */ /* * Server will not grant same Byte Range using a different Bucket Seq */ lck.in.lock_sequence = 0x010 + 0x2; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); torture_comment(torture, "Testing Lock Replay detection " "(Bucket No: 2):\n"); /* * Server will not grant same Byte Range using a different Bucket Num */ lck.in.lock_sequence = 0x020 + 0x1; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* status: still locked */ /* test with invalid bucket when file is locked */ torture_comment(torture, "Testing Lock Replay detection " "(Bucket No: 65 (invalid)) [ignored]:\n"); lck.in.lock_sequence = 0x410 + 0x1; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); /* status: unlocked */ /* * Lock again for the unlock replay test */ el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, "Testing Lock Replay detection " "(Bucket No: 64):\n"); /* * Server will not grant same Byte Range using a different Bucket Num */ lck.in.lock_sequence = 0x400 + 0x1; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* * Test Unlock replay detection */ lck.in.lock_sequence = 0x400 + 0x2; el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* new seq num ==> unlocked */ status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* replay detected ==> ignored */ el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); /* same seq num ==> ignored */ CHECK_STATUS(status, NT_STATUS_OK); /* verify it's unlocked: */ lck.in.lock_sequence = 0x400 + 0x3; el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); /* status: not locked */ done: smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } /** * Test lock replay detection * * This test check the SMB 3 behaviour of lock sequence checking, * which should be implemented for all handles. * * Make it clear that this test is supposed to pass a * server implementing the specification: * * [MS-SMB2] 3.3.5.14 Receiving an SMB2 LOCK Request * * ... * * ... if Open.IsResilient or Open.IsDurable or Open.IsPersistent is * TRUE or if Connection.Dialect belongs to the SMB 3.x dialect family * and Connection.ServerCapabilities includes * SMB2_GLOBAL_CAP_MULTI_CHANNEL bit, the server SHOULD<314> * perform lock sequence verification ... * * ... * * <314> Section 3.3.5.14: Windows 7 and Windows Server 2008 R2 perform * lock sequence verification only when Open.IsResilient is TRUE. * Windows 8 through Windows 10 v1909 and Windows Server 2012 through * Windows Server v1909 perform lock sequence verification only when * Open.IsResilient or Open.IsPersistent is TRUE. * * Note <314> also applies to all versions (at least) up to Windows Server v2004. * * Hopefully this will be fixed in future Windows versions and they * will avoid Note <314>. */ static bool _test_replay_smb3_specification(struct torture_context *torture, struct smb2_tree *tree, const char *testname, bool use_durable) { NTSTATUS status; bool ret = true; struct smb2_create io; struct smb2_handle h; struct smb2_lock lck; struct smb2_lock_element el; char fname[256]; struct smb2_transport *transport = tree->session->transport; if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { torture_skip(torture, "SMB 3.0.0 Dialect family or above \ required for Lock Replay tests\n"); } snprintf(fname, sizeof(fname), "%s\\%s.dat", BASEDIR, testname); status = torture_smb2_testdir(tree, BASEDIR, &h); CHECK_STATUS(status, NT_STATUS_OK); smb2_util_close(tree, h); torture_comment(torture, "%s: Testing Open File:\n", testname); smb2_oplock_create_share(&io, fname, smb2_util_share_access(""), smb2_util_oplock_level("b")); io.in.durable_open = use_durable; status = smb2_create(tree, torture, &io); CHECK_STATUS(status, NT_STATUS_OK); h = io.out.file.handle; CHECK_VALUE(io.out.oplock_level, smb2_util_oplock_level("b")); CHECK_VALUE(io.out.durable_open, use_durable); CHECK_VALUE(io.out.durable_open_v2, false); CHECK_VALUE(io.out.persistent_open, false); /* * Setup initial parameters */ el = (struct smb2_lock_element) { .length = 100, .offset = 100, }; lck = (struct smb2_lock) { .in.locks = &el, .in.lock_count = 0x0001, .in.file.handle = h }; torture_comment(torture, "Testing SMB 3 LockSequence for all Handles\n"); /* * Test with an invalid bucket number (only 1..64 are valid). * With an invalid number, lock replay detection is not performed. */ torture_comment(torture, "Testing Lock (ignored) Replay detection " "(Bucket No: 0 (invalid)) [ignored]:\n"); lck.in.lock_sequence = 0x000 + 0x1; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); torture_comment(torture, "Testing Lock Replay detection " "(Bucket No: 1):\n"); /* * Obtain Exclusive Lock of length 100 bytes using Bucket Num 1 * and Bucket Seq 1. */ lck.in.lock_sequence = 0x010 + 0x1; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* * Server detects Replay of Byte Range locks using the Lock Sequence * Numbers. And ignores the requests completely. */ status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* status: still locked */ /* * Server will not grant same Byte Range using a different Bucket Seq */ lck.in.lock_sequence = 0x010 + 0x2; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); torture_comment(torture, "Testing Lock Replay detection " "(Bucket No: 2):\n"); /* * Server will not grant same Byte Range using a different Bucket Num */ lck.in.lock_sequence = 0x020 + 0x1; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* status: still locked */ /* test with invalid bucket when file is locked */ torture_comment(torture, "Testing Lock Replay detection " "(Bucket No: 65 (invalid)) [ignored]:\n"); lck.in.lock_sequence = 0x410 + 0x1; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); /* status: unlocked */ /* * Lock again for the unlock replay test */ el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); torture_comment(torture, "Testing Lock Replay detection " "(Bucket No: 64):\n"); /* * Server will not grant same Byte Range using a different Bucket Num */ lck.in.lock_sequence = 0x400 + 0x1; el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_LOCK_NOT_GRANTED); /* * Test Unlock replay detection */ lck.in.lock_sequence = 0x400 + 0x2; el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* new seq num ==> unlocked */ status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_OK); /* replay detected ==> ignored */ el.flags = SMB2_LOCK_FLAG_EXCLUSIVE | SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; status = smb2_lock(tree, &lck); /* same seq num ==> ignored */ CHECK_STATUS(status, NT_STATUS_OK); /* verify it's unlocked: */ lck.in.lock_sequence = 0x400 + 0x3; el.flags = SMB2_LOCK_FLAG_UNLOCK; status = smb2_lock(tree, &lck); CHECK_STATUS(status, NT_STATUS_RANGE_NOT_LOCKED); /* status: not locked */ done: smb2_util_close(tree, h); smb2_deltree(tree, BASEDIR); return ret; } static bool test_replay_smb3_specification_durable(struct torture_context *torture, struct smb2_tree *tree) { const char *testname = "replay_smb3_specification_durable"; struct smb2_transport *transport = tree->session->transport; if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB2_10) { torture_skip(torture, "SMB 2.1.0 Dialect family or above \ required for Lock Replay tests on durable handles\n"); } return _test_replay_smb3_specification(torture, tree, testname, true); } static bool test_replay_smb3_specification_multi(struct torture_context *torture, struct smb2_tree *tree) { const char *testname = "replay_smb3_specification_multi"; struct smb2_transport *transport = tree->session->transport; uint32_t server_capabilities; if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) { torture_skip(torture, "SMB 3.0.0 Dialect family or above \ required for Lock Replay tests on without durable handles\n"); } server_capabilities = smb2cli_conn_server_capabilities(transport->conn); if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) { torture_skip(torture, "MULTI_CHANNEL is \ required for Lock Replay tests on without durable handles\n"); } return _test_replay_smb3_specification(torture, tree, testname, false); } /** * Test lock interaction between smbd and ctdb with tombstone records. * * Re-locking an unlocked record could lead to a deadlock between * smbd and ctdb. Make sure we don't regress. * * https://bugzilla.samba.org/show_bug.cgi?id=12005 * https://bugzilla.samba.org/show_bug.cgi?id=10008 */ static bool test_deadlock(struct torture_context *torture, struct smb2_tree *tree) { NTSTATUS status; bool ret = true; struct smb2_handle _h; struct smb2_handle *h = NULL; uint8_t buf[200]; const char *fname = BASEDIR "\\deadlock.txt"; if (!lpcfg_clustering(torture->lp_ctx)) { torture_skip(torture, "Test must be run on a ctdb cluster\n"); return true; } status = torture_smb2_testdir(tree, BASEDIR, &_h); torture_assert_ntstatus_ok(torture, status, "torture_smb2_testdir failed"); smb2_util_close(tree, _h); status = torture_smb2_testfile(tree, fname, &_h); torture_assert_ntstatus_ok_goto(torture, status, ret, done, "torture_smb2_testfile failed"); h = &_h; ZERO_STRUCT(buf); status = smb2_util_write(tree, *h, buf, 0, ARRAY_SIZE(buf)); torture_assert_ntstatus_ok_goto(torture, status, ret, done, "smb2_util_write failed"); status = test_smb2_lock(tree, *h, 0, 1, true); torture_assert_ntstatus_ok_goto(torture, status, ret, done, "test_smb2_lock failed"); status = test_smb2_unlock(tree, *h, 0, 1); torture_assert_ntstatus_ok_goto(torture, status, ret, done, "test_smb2_unlock failed"); status = test_smb2_lock(tree, *h, 0, 1, true); torture_assert_ntstatus_ok_goto(torture, status, ret, done, "test_smb2_lock failed"); status = test_smb2_unlock(tree, *h, 0, 1); torture_assert_ntstatus_ok_goto(torture, status, ret, done, "test_smb2_unlock failed"); done: if (h != NULL) { smb2_util_close(tree, *h); } smb2_deltree(tree, BASEDIR); return ret; } /* basic testing of SMB2 locking */ struct torture_suite *torture_smb2_lock_init(TALLOC_CTX *ctx) { struct torture_suite *suite = torture_suite_create(ctx, "lock"); torture_suite_add_1smb2_test(suite, "valid-request", test_valid_request); torture_suite_add_1smb2_test(suite, "rw-none", test_lock_rw_none); torture_suite_add_1smb2_test(suite, "rw-shared", test_lock_rw_shared); torture_suite_add_1smb2_test(suite, "rw-exclusive", test_lock_rw_exclusive); torture_suite_add_1smb2_test(suite, "auto-unlock", test_lock_auto_unlock); torture_suite_add_1smb2_test(suite, "lock", test_lock); torture_suite_add_1smb2_test(suite, "async", test_async); torture_suite_add_1smb2_test(suite, "cancel", test_cancel); torture_suite_add_1smb2_test(suite, "cancel-tdis", test_cancel_tdis); torture_suite_add_1smb2_test(suite, "cancel-logoff", test_cancel_logoff); torture_suite_add_1smb2_test(suite, "errorcode", test_errorcode); torture_suite_add_1smb2_test(suite, "zerobytelength", test_zerobytelength); torture_suite_add_1smb2_test(suite, "zerobyteread", test_zerobyteread); torture_suite_add_1smb2_test(suite, "unlock", test_unlock); torture_suite_add_1smb2_test(suite, "multiple-unlock", test_multiple_unlock); torture_suite_add_1smb2_test(suite, "stacking", test_stacking); torture_suite_add_1smb2_test(suite, "contend", test_contend); torture_suite_add_1smb2_test(suite, "context", test_context); torture_suite_add_1smb2_test(suite, "range", test_range); torture_suite_add_2smb2_test(suite, "overlap", test_overlap); torture_suite_add_1smb2_test(suite, "truncate", test_truncate); torture_suite_add_1smb2_test(suite, "replay_broken_windows", test_replay_broken_windows); torture_suite_add_1smb2_test(suite, "replay_smb3_specification_durable", test_replay_smb3_specification_durable); torture_suite_add_1smb2_test(suite, "replay_smb3_specification_multi", test_replay_smb3_specification_multi); torture_suite_add_1smb2_test(suite, "ctdb-delrec-deadlock", test_deadlock); suite->description = talloc_strdup(suite, "SMB2-LOCK tests"); return suite; }