summaryrefslogtreecommitdiffstats
path: root/source4/torture/smb2/lock.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/torture/smb2/lock.c')
-rw-r--r--source4/torture/smb2/lock.c3513
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;
+}