diff options
Diffstat (limited to 'source4/torture/smb2/lock.c')
-rw-r--r-- | source4/torture/smb2/lock.c | 3513 |
1 files changed, 3513 insertions, 0 deletions
diff --git a/source4/torture/smb2/lock.c b/source4/torture/smb2/lock.c new file mode 100644 index 0000000..eac0d55 --- /dev/null +++ b/source4/torture/smb2/lock.c @@ -0,0 +1,3513 @@ +/* + 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 <http://www.gnu.org/licenses/>. +*/ + +#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<torture_numops; i++) { + NEXT_OFFSET; + + lck.in.file.handle = h; + el[0].offset = offset - 1; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_OK, + talloc_asprintf(torture, + "lock h failed at offset %#llx ", + (unsigned long long) el[0].offset)); + + lck.in.file.handle = h2; + el[0].offset = offset - 2; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_OK, + talloc_asprintf(torture, + "lock h2 failed at offset %#llx ", + (unsigned long long) el[0].offset)); + } + + torture_comment(torture, " testing %d locks\n", torture_numops); + + for (offset=i=0; i<torture_numops; i++) { + NEXT_OFFSET; + + lck.in.file.handle = h; + el[0].offset = offset - 1; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_LOCK_NOT_GRANTED, + talloc_asprintf(torture, + "lock h at offset %#llx should not have " + "succeeded ", + (unsigned long long) el[0].offset)); + + lck.in.file.handle = h; + el[0].offset = offset - 2; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_LOCK_NOT_GRANTED, + talloc_asprintf(torture, + "lock h2 at offset %#llx should not have " + "succeeded ", + (unsigned long long) el[0].offset)); + + lck.in.file.handle = h2; + el[0].offset = offset - 1; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_LOCK_NOT_GRANTED, + talloc_asprintf(torture, + "lock h at offset %#llx should not have " + "succeeded ", + (unsigned long long) el[0].offset)); + + lck.in.file.handle = h2; + el[0].offset = offset - 2; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_LOCK_NOT_GRANTED, + talloc_asprintf(torture, + "lock h2 at offset %#llx should not have " + "succeeded ", + (unsigned long long) el[0].offset)); + } + + torture_comment(torture, " removing %d locks\n", torture_numops); + + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + + for (offset=i=0; i<torture_numops; i++) { + NEXT_OFFSET; + + lck.in.file.handle = h; + el[0].offset = offset - 1; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_OK, + talloc_asprintf(torture, + "unlock from h failed at offset %#llx ", + (unsigned long long) el[0].offset)); + + lck.in.file.handle = h2; + el[0].offset = offset - 2; + status = smb2_lock(tree, &lck); + CHECK_STATUS_CMT(status, NT_STATUS_OK, + talloc_asprintf(torture, + "unlock from h2 failed at offset %#llx ", + (unsigned long long) el[0].offset)); + } + +done: + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return ret; +} + +static NTSTATUS test_smb2_lock(struct smb2_tree *tree, struct smb2_handle h, + uint64_t offset, uint64_t length, bool exclusive) +{ + struct smb2_lock lck; + struct smb2_lock_element el[1]; + NTSTATUS status; + + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = offset; + el[0].length = length; + el[0].reserved = 0x00000000; + el[0].flags = (exclusive ? + SMB2_LOCK_FLAG_EXCLUSIVE : + SMB2_LOCK_FLAG_SHARED) | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + + status = smb2_lock(tree, &lck); + + return status; +} + +static NTSTATUS test_smb2_unlock(struct smb2_tree *tree, struct smb2_handle h, + uint64_t offset, uint64_t length) +{ + struct smb2_lock lck; + struct smb2_lock_element el[1]; + NTSTATUS status; + + lck.in.locks = el; + lck.in.lock_count = 0x0001; + lck.in.lock_sequence = 0x00000000; + lck.in.file.handle = h; + el[0].offset = offset; + el[0].length = length; + el[0].reserved = 0x00000000; + el[0].flags = SMB2_LOCK_FLAG_UNLOCK; + + status = smb2_lock(tree, &lck); + + return status; +} + +#define EXPECTED(ret, v) if ((ret) != (v)) { \ + torture_result(torture, TORTURE_FAIL, __location__": subtest failed");\ + torture_comment(torture, "** "); correct = false; \ + } + +/** + * Test overlapping lock ranges from various lockers + * - some tests ported from BASE-LOCK-LOCK4 + */ +static bool test_overlap(struct torture_context *torture, + struct smb2_tree *tree, + struct smb2_tree *tree2) +{ + NTSTATUS status; + bool ret = true; + struct smb2_handle h = {{0}}; + struct smb2_handle h2 = {{0}}; + struct smb2_handle h3 = {{0}}; + uint8_t buf[200]; + bool correct = true; + + const char *fname = BASEDIR "\\overlap.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); + + status = torture_smb2_testfile(tree2, fname, &h3); + CHECK_STATUS(status, NT_STATUS_OK); + + torture_comment(torture, "Testing overlapping locks:\n"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 0, 4, true)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h, 2, 4, true)); + EXPECTED(ret, false); + torture_comment(torture, "the same session/handle %s set overlapping " + "exclusive locks\n", ret?"can":"cannot"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 10, 4, false)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h, 12, 4, false)); + EXPECTED(ret, true); + torture_comment(torture, "the same session/handle %s set overlapping " + "shared locks\n", ret?"can":"cannot"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 20, 4, true)) && + NT_STATUS_IS_OK(test_smb2_lock(tree2, h3, 22, 4, true)); + EXPECTED(ret, false); + torture_comment(torture, "a different session %s set overlapping " + "exclusive locks\n", ret?"can":"cannot"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 30, 4, false)) && + NT_STATUS_IS_OK(test_smb2_lock(tree2, h3, 32, 4, false)); + EXPECTED(ret, true); + torture_comment(torture, "a different session %s set overlapping " + "shared locks\n", ret?"can":"cannot"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 40, 4, true)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h2, 42, 4, true)); + EXPECTED(ret, false); + torture_comment(torture, "a different handle %s set overlapping " + "exclusive locks\n", ret?"can":"cannot"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 50, 4, false)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h2, 52, 4, false)); + EXPECTED(ret, true); + torture_comment(torture, "a different handle %s set overlapping " + "shared locks\n", ret?"can":"cannot"); + + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 110, 4, false)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h, 112, 4, false)) && + NT_STATUS_IS_OK(test_smb2_unlock(tree, h, 110, 6)); + EXPECTED(ret, false); + torture_comment(torture, "the same handle %s coalesce read locks\n", + ret?"can":"cannot"); + + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + 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); + ret = NT_STATUS_IS_OK(test_smb2_lock(tree, h, 0, 8, false)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h2, 0, 1, false)) && + NT_STATUS_IS_OK(smb2_util_close(tree, h)) && + NT_STATUS_IS_OK(torture_smb2_testfile(tree, fname, &h)) && + NT_STATUS_IS_OK(test_smb2_lock(tree, h, 7, 1, true)); + EXPECTED(ret, true); + torture_comment(torture, "the server %s have the NT byte range lock " + "bug\n", !ret?"does":"doesn't"); + +done: + smb2_util_close(tree2, h3); + smb2_util_close(tree, h2); + smb2_util_close(tree, h); + smb2_deltree(tree, BASEDIR); + return correct; +} + +/** + * Test truncation of locked file + * - some tests ported from BASE-LOCK-LOCK7 + */ +static bool test_truncate(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_create io; + + const char *fname = BASEDIR "\\truncate.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 truncation of locked file:\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; + + ZERO_STRUCT(io); + io.in.oplock_level = 0; + io.in.desired_access = SEC_RIGHTS_FILE_ALL; + io.in.file_attributes = FILE_ATTRIBUTE_NORMAL; + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + io.in.share_access = NTCREATEX_SHARE_ACCESS_DELETE | + NTCREATEX_SHARE_ACCESS_READ | + NTCREATEX_SHARE_ACCESS_WRITE; + io.in.create_options = 0; + io.in.fname = fname; + + /* Take an exclusive lock */ + el[0].flags = SMB2_LOCK_FLAG_EXCLUSIVE | + SMB2_LOCK_FLAG_FAIL_IMMEDIATELY; + status = smb2_lock(tree, &lck); + CHECK_STATUS(status, NT_STATUS_OK); + + /* On second handle open the file with OVERWRITE disposition */ + torture_comment(torture, " overwrite disposition is allowed on a " + "locked file.\n"); + + io.in.create_disposition = NTCREATEX_DISP_OVERWRITE; + status = smb2_create(tree, tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + smb2_util_close(tree, h2); + + /* On second handle open the file with SUPERSEDE disposition */ + torture_comment(torture, " supersede disposition is allowed on a " + "locked file.\n"); + + io.in.create_disposition = NTCREATEX_DISP_SUPERSEDE; + status = smb2_create(tree, tree, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h2 = io.out.file.handle; + smb2_util_close(tree, h2); + + /* 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 lock replay detection + * + * This test checks the SMB 2.1.0 behaviour of lock sequence checking, + * which is only turned on for resilient handles. + * + * Make it clear that this test is supposed to pass against the legacy + * Windows servers which violate 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_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; +} |