[SCM] Samba Shared Repository - branch master updated

Volker Lendecke vlendec at samba.org
Fri Aug 15 16:52:01 UTC 2025


The branch, master has been updated
       via  65d86082338 smbd: improve lease break when handling overwrite create disposition
       via  b2f7a1c7623 smbtorture: add test "smb2.lease.sharing_violation"
       via  215b2c741a9 smbd: when going to truncate the file, explicitly set the filesize to 0
       via  1fcc1500eed smbtorture: add test smb2.lease.lock3
       via  debcd77ae78 s3/locking: fix checking for byterange locks when granting RH lease
       via  0fdd231f80e s3/locking: modernize file_has_brlocks()
       via  f26ae02a1bf smbd: make file_has_brlocks() public
       via  196efe52814 smbd: avoid granting "H"-only lease
       via  9eba1a1423b smbtorture: add test smb2.lease.lock2
      from  0be53d7ac0a smbd: return correct reparse tag DFS when listing directories

https://git.samba.org/?p=samba.git;a=shortlog;h=master


- Log -----------------------------------------------------------------
commit 65d86082338c0b46358520ba5134b3f9c39291ee
Author: Ralph Boehme <slow at samba.org>
Date:   Fri Aug 8 13:52:59 2025 +0200

    smbd: improve lease break when handling overwrite create disposition
    
    If the contending create uses overwrite create disposition, but has caused a
    sharing violation and the existing create has a SMB2_LEASE_HANDLE, then the
    server should just send break the SMB2_LEASE_HANDLE.
    
    The break will then either result in a close and the contending open succeeds,
    or a STATUS_SHARING_VIOLATION. Either way, there's no need to additionally break
    SMB2_LEASE_READ or SMB2_LEASE_WRITE.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15894
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Reviewed-by: Volker Lendecke <vl at samba.org>
    
    Autobuild-User(master): Volker Lendecke <vl at samba.org>
    Autobuild-Date(master): Fri Aug 15 16:51:05 UTC 2025 on atb-devel-224

commit b2f7a1c7623248b3a42a0ba3e07ab523403d9f9b
Author: Ralph Boehme <slow at samba.org>
Date:   Sat Aug 9 12:31:17 2025 +0200

    smbtorture: add test "smb2.lease.sharing_violation"
    
    Verifies an existing RWH lease on a file is only broken to RW when a contending
    create fails with STATUS_SHARING_VIOLATION.
    
    Passes against Windows, fails against Samba.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15894
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Reviewed-by: Volker Lendecke <vl at samba.org>

commit 215b2c741a93023d13e8a9f82739ac3e91b64a66
Author: Ralph Boehme <slow at samba.org>
Date:   Thu Aug 7 19:15:43 2025 +0200

    smbd: when going to truncate the file, explicitly set the filesize to 0
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15894
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Reviewed-by: Volker Lendecke <vl at samba.org>

commit 1fcc1500eedf4c08d1245c8c71d8002e32d8e40f
Author: Ralph Boehme <slow at samba.org>
Date:   Sat Aug 9 11:53:23 2025 +0200

    smbtorture: add test smb2.lease.lock3
    
    Verifies a create with overwrite disposition on a file with a byterange lock can
    get an RH lease.
    
    Passes against Windows, fails against Samba.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15894
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Reviewed-by: Volker Lendecke <vl at samba.org>

commit debcd77ae7818ec953058e7524642ef76e0e6c7b
Author: Ralph Boehme <slow at samba.org>
Date:   Thu Aug 7 18:44:27 2025 +0200

    s3/locking: fix checking for byterange locks when granting RH lease
    
    From MS-FSA 2.1.5.18 "Server Requests an Oplock":
    
    ...
    
    * Else If Type is LEVEL_GRANULAR:
        * If RequestedOplockLevel is READ_CACHING or (READ_CACHING|HANDLE_CACHING):
            * The operation MUST be failed with STATUS_OPLOCK_NOT_GRANTED under either of the
              following conditions:
                * Open.Stream.ByteRangeLockList is not empty and Open.Stream.AllocationSize
                  is greater than any ByteRangeLock.LockOffset in
                  Open.Stream.ByteRangeLockList.
    
    ...
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15894
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Reviewed-by: Volker Lendecke <vl at samba.org>

commit 0fdd231f80eb9afe783e065c50b0f93c813d0857
Author: Ralph Boehme <slow at samba.org>
Date:   Sat Aug 9 11:41:45 2025 +0200

    s3/locking: modernize file_has_brlocks()
    
    No change in behaviour. Minimizes diff in the next commit that introduce a
    behaviour change.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15894
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Reviewed-by: Volker Lendecke <vl at samba.org>

commit f26ae02a1bf739f25975143d1e1706d30ff98b2f
Author: Ralph Boehme <slow at samba.org>
Date:   Sat Aug 9 11:39:55 2025 +0200

    smbd: make file_has_brlocks() public
    
    Prepares for a change to file_has_brlocks() in the next commit. No change in
    behaviour.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15894
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Reviewed-by: Volker Lendecke <vl at samba.org>

commit 196efe52814a24cd4a8c47346226407af17eadbd
Author: Ralph Boehme <slow at samba.org>
Date:   Mon Jun 2 12:07:26 2025 +0200

    smbd: avoid granting "H"-only lease
    
    If an "RH" lease was requested and due to existing brl-lock we do not grant
    an "R" lease, we end up granting an "H"-only lease which is not a valid lease
    state.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15894
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Reviewed-by: Volker Lendecke <vl at samba.org>

commit 9eba1a1423be253216ea862029cee2398961f7d4
Author: Ralph Boehme <slow at samba.org>
Date:   Sat Aug 9 09:09:47 2025 +0200

    smbtorture: add test smb2.lease.lock2
    
    Verifies byterange locks only affect lease state if the lock is actually
    "backed" by the file. Eg, if a file has size 0, byterange locks will never
    affect lease state.
    
    Passes against Windows, fails against Samba.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15894
    
    Signed-off-by: Ralph Boehme <slow at samba.org>
    Reviewed-by: Volker Lendecke <vl at samba.org>

-----------------------------------------------------------------------

Summary of changes:
 source3/locking/brlock.c     |  25 +++++
 source3/locking/proto.h      |   1 +
 source3/smbd/open.c          |  26 ++---
 source4/torture/smb2/lease.c | 258 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 296 insertions(+), 14 deletions(-)


Changeset truncated at 500 lines:

diff --git a/source3/locking/brlock.c b/source3/locking/brlock.c
index 787c65c16f8..e0f5c14c302 100644
--- a/source3/locking/brlock.c
+++ b/source3/locking/brlock.c
@@ -1998,3 +1998,28 @@ void brl_set_modified(struct byte_range_lock *br_lck, bool modified)
 {
 	br_lck->modified = modified;
 }
+
+bool file_has_brlocks(files_struct *fsp)
+{
+	struct byte_range_lock *br_lck = NULL;
+	uint i, num_locks;
+
+	br_lck = brl_get_locks_readonly(fsp);
+	if (br_lck == NULL) {
+		return false;
+	}
+
+	num_locks = brl_num_locks(br_lck);
+	if (num_locks == 0) {
+		return false;
+	}
+
+	for (i = 0; i < num_locks; i++) {
+		struct lock_struct *l = &br_lck->lock_data[i];
+
+		if (l->start < fsp->fsp_name->st.st_ex_size) {
+			return true;
+		}
+	}
+	return false;
+}
diff --git a/source3/locking/proto.h b/source3/locking/proto.h
index 29a4092c805..ab19632abb7 100644
--- a/source3/locking/proto.h
+++ b/source3/locking/proto.h
@@ -105,6 +105,7 @@ struct byte_range_lock *brl_get_locks(TALLOC_CTX *mem_ctx,
 struct byte_range_lock *brl_get_locks_readonly(files_struct *fsp);
 bool brl_cleanup_disconnected(struct file_id fid, uint64_t open_persistent_id);
 void brl_set_modified(struct byte_range_lock *br_lck, bool modified);
+bool file_has_brlocks(files_struct *fsp);
 
 /* The following definitions come from locking/locking.c  */
 
diff --git a/source3/smbd/open.c b/source3/smbd/open.c
index e50b6b68fab..e898829c52c 100644
--- a/source3/smbd/open.c
+++ b/source3/smbd/open.c
@@ -1879,17 +1879,6 @@ static bool is_same_lease(const files_struct *fsp,
 				&e->lease_key);
 }
 
-static bool file_has_brlocks(files_struct *fsp)
-{
-	struct byte_range_lock *br_lck;
-
-	br_lck = brl_get_locks_readonly(fsp);
-	if (!br_lck)
-		return false;
-
-	return (brl_num_locks(br_lck) > 0);
-}
-
 struct fsp_lease *find_fsp_lease(struct files_struct *new_fsp,
 				 const struct smb2_lease_key *key,
 				 uint32_t current_state,
@@ -2280,7 +2269,7 @@ static bool delay_for_oplock_fn(
 
 	break_to = e_lease_type & ~state->delay_mask;
 
-	if (state->will_overwrite) {
+	if (state->will_overwrite && !(state->delay_mask & SMB2_LEASE_HANDLE)) {
 		break_to &= ~(SMB2_LEASE_HANDLE|SMB2_LEASE_READ);
 	}
 
@@ -2299,7 +2288,7 @@ static bool delay_for_oplock_fn(
 		return false;
 	}
 
-	if (state->will_overwrite) {
+	if (state->will_overwrite && !(state->delay_mask & SMB2_LEASE_HANDLE)) {
 		/*
 		 * If we break anyway break to NONE directly.
 		 * Otherwise vfs_set_filelen() will trigger the
@@ -2500,7 +2489,7 @@ grant:
 	if (lp_locking(fsp->conn->params) && file_has_brlocks(fsp)) {
 		DBG_DEBUG("file %s has byte range locks\n",
 			  fsp_str_dbg(fsp));
-		granted &= ~SMB2_LEASE_READ;
+		granted &= ~(SMB2_LEASE_READ | SMB2_LEASE_HANDLE);
 	}
 
 	if (state.disallow_write_lease) {
@@ -4074,6 +4063,15 @@ static NTSTATUS open_file_ntcreate(connection_struct *conn,
 	} else {
 		if (flags & O_TRUNC) {
 			info = FILE_WAS_OVERWRITTEN;
+			/*
+			 * We did not truncate the file yet, we're doing that
+			 * explicitly with SMB_VFS_FTRUNCATE() below under the
+			 * sharemode glock. For correct handling of RH leases in
+			 * the presence of byterange locks, the leases code
+			 * needs the "correct" filesize which should be 0 at
+			 * this place if we did the O_TRUNC at open() time.
+			 */
+			fsp->fsp_name->st.st_ex_size = 0;
 		} else {
 			info = FILE_WAS_OPENED;
 		}
diff --git a/source4/torture/smb2/lease.c b/source4/torture/smb2/lease.c
index c2bcda1d887..de6c2dd69ad 100644
--- a/source4/torture/smb2/lease.c
+++ b/source4/torture/smb2/lease.c
@@ -3533,6 +3533,261 @@ done:
 	return ret;
 }
 
+/*
+ * Verifies byterange locks only affect lease state if the lock is actually
+ * "backed" by the file. Eg, if a file has size 0, byterange locks will never
+ * affect lease state.
+ *
+ * Client 1: create file with lease=RWH
+ * Client 1: set brl off=0, size=1
+ * Client 2: open file, expect pending
+ * Server: expect lease break to RH
+ * Client 2: expect open success with lease=RH
+ */
+static bool test_lease_lock2(struct torture_context *tctx,
+			     struct smb2_tree *tree1,
+			     struct smb2_tree *tree2)
+{
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_create io1 = {};
+	struct smb2_create io2 = {};
+	struct smb2_lease ls1 = {};
+	struct smb2_lease ls2 = {};
+	struct smb2_handle h1 = {};
+	struct smb2_handle h2 = {};
+	struct smb2_lock lck = {};
+	struct smb2_lock_element el = {};
+	const char *fname = __FUNCTION__;
+	bool ret = true;
+	NTSTATUS status;
+	uint32_t caps;
+
+	caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+	torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "leases are not supported");
+
+	/* Set up handlers. */
+	tree1->session->transport->lease.handler = torture_lease_handler;
+	tree1->session->transport->lease.private_data = tree1;
+	tree2->session->transport->lease.handler = torture_lease_handler;
+	tree2->session->transport->lease.private_data = tree2;
+
+	smb2_util_unlink(tree1, fname);
+
+	torture_reset_lease_break_info(tctx, &lease_break_info);
+
+	/* Open a handle on tree1. */
+	smb2_lease_create_share(&io1, &ls1, false, fname,
+				smb2_util_share_access("RWD"),
+				LEASE1,
+				smb2_util_lease_state("RWH"));
+	status = smb2_create(tree1, mem_ctx, &io1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	h1 = io1.out.file.handle;
+	CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_LEASE(&io1, "RWH", true, LEASE1, 0);
+
+	/*
+	 * Try and get get an exclusive byte
+	 * range lock on H1 (LEASE1).
+	 */
+	lck.in.locks		= ⪙
+	lck.in.lock_count	= 1;
+	lck.in.lock_sequence	= 1;
+	lck.in.file.handle	= h1;
+	el.offset		= 0;
+	el.length		= 1;
+	el.reserved		= 0;
+	el.flags		= SMB2_LOCK_FLAG_EXCLUSIVE;
+	status = smb2_lock(tree1, &lck);
+	CHECK_STATUS(status, NT_STATUS_OK);
+
+	/* Open a second handle on tree2. */
+	smb2_lease_create_share(&io2, &ls2, false, fname,
+				smb2_util_share_access("RWD"),
+				LEASE2,
+				smb2_util_lease_state("RWH"));
+	status = smb2_create(tree2, mem_ctx, &io2);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	h2 = io2.out.file.handle;
+	CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_LEASE(&io2, "RH", true, LEASE2, 0);
+	/* And LEASE1 got broken to RH. */
+	CHECK_BREAK_INFO("RWH", "RH", LEASE1);
+	torture_reset_lease_break_info(tctx, &lease_break_info);
+
+done:
+	smb2_util_close(tree1, h1);
+	smb2_util_close(tree2, h2);
+
+	smb2_util_unlink(tree1, fname);
+	talloc_free(mem_ctx);
+	return ret;
+}
+
+/*
+ * Verifies a create with overwrite disposition on a file with a byterange lock
+ * can get an RH lease.
+ *
+ * Client 1: create file with lease=RWH
+ * Client 1: write 1 byte to the file
+ * Client 1: set brl off=0, size=1
+ * Client 2: open file with overwrite disposition, expect status pending
+ * Server -> Client 1: Break lease break to none
+ * Client 2: expect open success with lease=RH
+ */
+static bool test_lease_lock3(struct torture_context *tctx,
+			     struct smb2_tree *tree1,
+			     struct smb2_tree *tree2)
+{
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_create io1 = {};
+	struct smb2_create io2 = {};
+	struct smb2_lease ls1 = {};
+	struct smb2_lease ls2 = {};
+	struct smb2_handle h1 = {};
+	struct smb2_handle h2 = {};
+	struct smb2_lock lck = {};
+	struct smb2_lock_element el = {};
+	const char *fname = __FUNCTION__;
+	char c = 'x';
+	bool ret = true;
+	NTSTATUS status;
+	uint32_t caps;
+
+	caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+	torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "leases are not supported");
+
+	/* Set up handlers. */
+	tree1->session->transport->lease.handler = torture_lease_handler;
+	tree1->session->transport->lease.private_data = tree1;
+	tree2->session->transport->lease.handler = torture_lease_handler;
+	tree2->session->transport->lease.private_data = tree2;
+
+	smb2_util_unlink(tree1, fname);
+
+	torture_reset_lease_break_info(tctx, &lease_break_info);
+
+	/* Open a handle on tree1. */
+	smb2_lease_create_share(&io1, &ls1, false, fname,
+				smb2_util_share_access("RWD"),
+				LEASE1,
+				smb2_util_lease_state("RWH"));
+	status = smb2_create(tree1, mem_ctx, &io1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	h1 = io1.out.file.handle;
+	CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_LEASE(&io1, "RWH", true, LEASE1, 0);
+
+	status = smb2_util_write(tree1, h1, &c, 0, 1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+
+	/*
+	 * Try and get an exclusive byte
+	 * range lock on H1 (LEASE1).
+	 */
+	lck.in.locks		= ⪙
+	lck.in.lock_count	= 1;
+	lck.in.lock_sequence	= 1;
+	lck.in.file.handle	= h1;
+	el.offset		= 0;
+	el.length		= 1;
+	el.reserved		= 0;
+	el.flags		= SMB2_LOCK_FLAG_EXCLUSIVE;
+	status = smb2_lock(tree1, &lck);
+	CHECK_STATUS(status, NT_STATUS_OK);
+
+	/* Open a second handle on tree2. */
+	smb2_lease_create_share(&io2, &ls2, false, fname,
+				smb2_util_share_access("RWD"),
+				LEASE2,
+				smb2_util_lease_state("RWH"));
+	io2.in.create_disposition = NTCREATEX_DISP_OVERWRITE;
+	status = smb2_create(tree2, mem_ctx, &io2);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	h2 = io2.out.file.handle;
+	CHECK_LEASE(&io2, "RH", true, LEASE2, 0);
+	/* And LEASE1 got broken to NONE. */
+	CHECK_BREAK_INFO("RWH", "", LEASE1);
+	torture_reset_lease_break_info(tctx, &lease_break_info);
+
+done:
+	smb2_util_close(tree1, h1);
+	smb2_util_close(tree2, h2);
+
+	smb2_util_unlink(tree1, fname);
+	talloc_free(mem_ctx);
+	return ret;
+}
+
+/*
+ * Verifies an existing RWH lease on a file is only broken to RW when a
+ * contending create fails with STATUS_SHARING_VIOLATION.
+ *
+ * Client 1: open file with lease=RWH sharemode=none
+ * Client 2: open file, expect STATUS_PENDING
+ * Server: send lease break to RW to client 1
+ * Client 2: expect open to fail with STATUS_SHARING_VIOLATION.
+ */
+static bool test_lease_sharing_violation(struct torture_context *tctx,
+					 struct smb2_tree *tree1,
+					 struct smb2_tree *tree2)
+{
+	TALLOC_CTX *mem_ctx = talloc_new(tctx);
+	struct smb2_create io1 = {};
+	struct smb2_create io2 = {};
+	struct smb2_lease ls1 = {};
+	struct smb2_lease ls2 = {};
+	struct smb2_handle h1 = {};
+	struct smb2_handle h2 = {};
+	const char *fname = __FUNCTION__;
+	bool ret = true;
+	NTSTATUS status;
+	uint32_t caps;
+
+	caps = smb2cli_conn_server_capabilities(tree1->session->transport->conn);
+	torture_assert_goto(tctx, caps & SMB2_CAP_LEASING, ret, done, "leases are not supported");
+
+	/* Set up handlers. */
+	tree1->session->transport->lease.handler = torture_lease_handler;
+	tree1->session->transport->lease.private_data = tree1;
+	tree2->session->transport->lease.handler = torture_lease_handler;
+	tree2->session->transport->lease.private_data = tree2;
+
+	smb2_util_unlink(tree1, fname);
+
+	torture_reset_lease_break_info(tctx, &lease_break_info);
+
+	/* Open a handle on tree1. */
+	smb2_lease_create_share(&io1, &ls1, false, fname,
+				smb2_util_share_access(""),
+				LEASE1,
+				smb2_util_lease_state("RWH"));
+	status = smb2_create(tree1, mem_ctx, &io1);
+	CHECK_STATUS(status, NT_STATUS_OK);
+	h1 = io1.out.file.handle;
+	CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+	CHECK_LEASE(&io1, "RWH", true, LEASE1, 0);
+
+	/* Open a second handle on tree2. */
+	smb2_lease_create_share(&io2, &ls2, false, fname,
+				smb2_util_share_access("RWD"),
+				LEASE2,
+				smb2_util_lease_state("RWH"));
+	io2.in.create_disposition = NTCREATEX_DISP_OVERWRITE;
+	status = smb2_create(tree2, mem_ctx, &io2);
+	CHECK_STATUS(status, NT_STATUS_SHARING_VIOLATION);
+	/* And LEASE1 got broken to RW. */
+	CHECK_BREAK_INFO("RWH", "RW", LEASE1);
+
+done:
+	smb2_util_close(tree1, h1);
+	smb2_util_close(tree2, h2);
+
+	smb2_util_unlink(tree1, fname);
+	talloc_free(mem_ctx);
+	return ret;
+}
+
 static bool test_lease_complex1(struct torture_context *tctx,
 				struct smb2_tree *tree1a)
 {
@@ -5863,6 +6118,9 @@ struct torture_suite *torture_smb2_lease_init(TALLOC_CTX *ctx)
 	torture_suite_add_1smb2_test(suite, "breaking5", test_lease_breaking5);
 	torture_suite_add_1smb2_test(suite, "breaking6", test_lease_breaking6);
 	torture_suite_add_2smb2_test(suite, "lock1", test_lease_lock1);
+	torture_suite_add_2smb2_test(suite, "lock2", test_lease_lock2);
+	torture_suite_add_2smb2_test(suite, "lock3", test_lease_lock3);
+	torture_suite_add_2smb2_test(suite, "sharing_violation", test_lease_sharing_violation);
 	torture_suite_add_1smb2_test(suite, "complex1", test_lease_complex1);
 	torture_suite_add_1smb2_test(suite, "v2_flags_breaking", test_lease_v2_flags_breaking);
 	torture_suite_add_1smb2_test(suite, "v2_flags_parentkey", test_lease_v2_flags_parentkey);


-- 
Samba Shared Repository



More information about the samba-cvs mailing list