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 <s...@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 <s...@samba.org> Reviewed-by: Volker Lendecke <v...@samba.org> Autobuild-User(master): Volker Lendecke <v...@samba.org> Autobuild-Date(master): Fri Aug 15 16:51:05 UTC 2025 on atb-devel-224 commit b2f7a1c7623248b3a42a0ba3e07ab523403d9f9b Author: Ralph Boehme <s...@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 <s...@samba.org> Reviewed-by: Volker Lendecke <v...@samba.org> commit 215b2c741a93023d13e8a9f82739ac3e91b64a66 Author: Ralph Boehme <s...@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 <s...@samba.org> Reviewed-by: Volker Lendecke <v...@samba.org> commit 1fcc1500eedf4c08d1245c8c71d8002e32d8e40f Author: Ralph Boehme <s...@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 <s...@samba.org> Reviewed-by: Volker Lendecke <v...@samba.org> commit debcd77ae7818ec953058e7524642ef76e0e6c7b Author: Ralph Boehme <s...@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 <s...@samba.org> Reviewed-by: Volker Lendecke <v...@samba.org> commit 0fdd231f80eb9afe783e065c50b0f93c813d0857 Author: Ralph Boehme <s...@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 <s...@samba.org> Reviewed-by: Volker Lendecke <v...@samba.org> commit f26ae02a1bf739f25975143d1e1706d30ff98b2f Author: Ralph Boehme <s...@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 <s...@samba.org> Reviewed-by: Volker Lendecke <v...@samba.org> commit 196efe52814a24cd4a8c47346226407af17eadbd Author: Ralph Boehme <s...@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 <s...@samba.org> Reviewed-by: Volker Lendecke <v...@samba.org> commit 9eba1a1423be253216ea862029cee2398961f7d4 Author: Ralph Boehme <s...@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 <s...@samba.org> Reviewed-by: Volker Lendecke <v...@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