From e01391cf4320de6f44da91167197f6bfc415e0b3 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Thu, 4 Dec 2014 10:13:47 -0800 Subject: [PATCH 1/6] s3: leases: Add leases_db_rename() to cope with renaming a leased file. Signed-off-by: Jeremy Allison --- source3/locking/leases_db.c | 22 ++++++++++++++++++++++ source3/locking/leases_db.h | 6 +++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/source3/locking/leases_db.c b/source3/locking/leases_db.c index 67c93ff..7e000aa 100644 --- a/source3/locking/leases_db.c +++ b/source3/locking/leases_db.c @@ -385,3 +385,25 @@ NTSTATUS leases_db_parse(const struct GUID *client_guid, } return state.status; } + +NTSTATUS leases_db_rename(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + const struct file_id *id, + const char *filename_new, + const char *stream_name_new) +{ + NTSTATUS status; + + status = leases_db_del(client_guid, + lease_key, + id); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return leases_db_add(client_guid, + lease_key, + id, + filename_new, + stream_name_new); +} diff --git a/source3/locking/leases_db.h b/source3/locking/leases_db.h index f570356..906a99b 100644 --- a/source3/locking/leases_db.h +++ b/source3/locking/leases_db.h @@ -42,5 +42,9 @@ NTSTATUS leases_db_parse(const struct GUID *client_guid, const char *stream_name, void *private_data), void *private_data); - +NTSTATUS leases_db_rename(const struct GUID *client_guid, + const struct smb2_lease_key *lease_key, + const struct file_id *id, + const char *filename_new, + const char *stream_name_new); #endif /* _LEASES_DB_H_ */ -- 1.9.1 From 3898a971b9c4c1f5272ee4722a0afe35be899e12 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Thu, 4 Dec 2014 10:14:23 -0800 Subject: [PATCH 2/6] s3: leases : Cope with renaming leased open files. Signed-off-by: Jeremy Allison --- source3/locking/locking.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/source3/locking/locking.c b/source3/locking/locking.c index d144f5c..4ff23f2 100644 --- a/source3/locking/locking.c +++ b/source3/locking/locking.c @@ -471,7 +471,7 @@ bool rename_share_filename(struct messaging_context *msg_ctx, size_t sn_len; size_t msg_len; char *frm = NULL; - int i; + uint32_t i; bool strip_two_chars = false; bool has_stream = smb_fname_dst->stream_name != NULL; struct server_id self_pid = messaging_server_id(msg_ctx); @@ -565,6 +565,29 @@ bool rename_share_filename(struct messaging_context *msg_ctx, (uint8 *)frm, msg_len); } + for (i=0; inum_leases; i++) { + /* Update the filename in leases_db. */ + NTSTATUS status; + struct share_mode_lease *l; + + l = &d->leases[i]; + + status = leases_db_rename(&l->client_guid, + &l->lease_key, + &id, + d->base_name, + d->stream_name); + if (!NT_STATUS_IS_OK(status)) { + /* Any error recovery possible here ? */ + DEBUG(10,("Failed to rename lease key for " + "renamed file %s:%s. %s\n", + d->base_name, + d->stream_name, + nt_errstr(status))); + continue; + } + } + return True; } -- 1.9.1 From b3821791b09f594f891c8cc8ea80a20712385c1f Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Thu, 4 Dec 2014 21:13:33 -0800 Subject: [PATCH 3/6] s3: leases: Make aio_add_req_to_fsp() public. We're going to need this to stop handle closures with outstanding async SMB2 renames causing a crash. Signed-off-by: Jeremy Allison --- source3/smbd/aio.c | 2 +- source3/smbd/proto.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/source3/smbd/aio.c b/source3/smbd/aio.c index 56b4cfc..e2306a9 100644 --- a/source3/smbd/aio.c +++ b/source3/smbd/aio.c @@ -115,7 +115,7 @@ static int aio_del_req_from_fsp(struct aio_req_fsp_link *lnk) return 0; } -static bool aio_add_req_to_fsp(files_struct *fsp, struct tevent_req *req) +bool aio_add_req_to_fsp(files_struct *fsp, struct tevent_req *req) { size_t array_len; struct aio_req_fsp_link *lnk; diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h index e40a77b..3798555 100644 --- a/source3/smbd/proto.h +++ b/source3/smbd/proto.h @@ -91,6 +91,7 @@ NTSTATUS schedule_aio_smb2_write(connection_struct *conn, DATA_BLOB in_data, bool write_through); bool cancel_smb2_aio(struct smb_request *smbreq); +bool aio_add_req_to_fsp(files_struct *fsp, struct tevent_req *req); /* The following definitions come from smbd/blocking.c */ -- 1.9.1 From 030501f497ddf26cd318fd6d305f79128be2bb43 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Thu, 4 Dec 2014 21:15:24 -0800 Subject: [PATCH 4/6] s3: leases: send_break_message() public. We're going to need this to allow async SMB2 setinfo renames to send lease break messages as well as the open code. Signed-off-by: Jeremy Allison --- source3/smbd/open.c | 2 +- source3/smbd/proto.h | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/source3/smbd/open.c b/source3/smbd/open.c index c6b67f4..8f19a36 100644 --- a/source3/smbd/open.c +++ b/source3/smbd/open.c @@ -1244,7 +1244,7 @@ static NTSTATUS open_mode_check(connection_struct *conn, * our client. */ -static NTSTATUS send_break_message(struct messaging_context *msg_ctx, +NTSTATUS send_break_message(struct messaging_context *msg_ctx, const struct share_mode_entry *exclusive, uint16_t break_to) { diff --git a/source3/smbd/proto.h b/source3/smbd/proto.h index 3798555..9980d03 100644 --- a/source3/smbd/proto.h +++ b/source3/smbd/proto.h @@ -616,6 +616,9 @@ NTSTATUS change_dir_owner_to_parent(connection_struct *conn, const char *fname, SMB_STRUCT_STAT *psbuf); bool is_stat_open(uint32 access_mask); +NTSTATUS send_break_message(struct messaging_context *msg_ctx, + const struct share_mode_entry *exclusive, + uint16_t break_to); struct deferred_open_record; bool is_deferred_open_async(const struct deferred_open_record *rec); NTSTATUS create_directory(connection_struct *conn, struct smb_request *req, -- 1.9.1 From ea72f5da9af0d08f96e088fead80193e0464bdf9 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Thu, 4 Dec 2014 21:19:32 -0800 Subject: [PATCH 5/6] s3: leases: Make SMB2 setinfo SMB2_FILE_RENAME_INFORMATION_INTERNAL async. If there are any RH leases we must break them to read and must wait for the client response before doing the rename. Signed-off-by: Jeremy Allison --- source3/smbd/smb2_setinfo.c | 200 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/source3/smbd/smb2_setinfo.c b/source3/smbd/smb2_setinfo.c index d95bd3d..6fe0b40 100644 --- a/source3/smbd/smb2_setinfo.c +++ b/source3/smbd/smb2_setinfo.c @@ -25,6 +25,9 @@ #include "../libcli/smb/smb_common.h" #include "trans2.h" #include "../lib/util/tevent_ntstatus.h" +#include "../librpc/gen_ndr/open_files.h" +#include "source3/lib/dbwrap/dbwrap_watch.h" +#include "messages.h" static struct tevent_req *smbd_smb2_setinfo_send(TALLOC_CTX *mem_ctx, struct tevent_context *ev, @@ -156,6 +159,168 @@ static void smbd_smb2_request_setinfo_done(struct tevent_req *subreq) } } +struct defer_rename_state { + struct tevent_req *req; + struct smbd_smb2_request *smb2req; + struct tevent_context *ev; + struct files_struct *fsp; + char *data; + int data_size; +}; + +static void defer_rename_done(struct tevent_req *subreq); + +static struct tevent_req *delay_rename_for_lease_break(struct tevent_req *req, + struct smbd_smb2_request *smb2req, + struct tevent_context *ev, + struct files_struct *fsp, + struct share_mode_lock *lck, + char *data, + int data_size) + +{ + struct tevent_req *subreq; + uint32_t i; + struct share_mode_data *d = lck->data; + struct defer_rename_state *rename_state; + bool delay = false; + struct timeval timeout; + + if (fsp->oplock_type != LEASE_OPLOCK) { + return NULL; + } + + for (i=0; inum_share_modes; i++) { + struct share_mode_entry *e = &d->share_modes[i]; + struct share_mode_lease *l = NULL; + uint32_t e_lease_type = get_lease_type(d, e); + uint32_t break_to; + + if (e->op_type != LEASE_OPLOCK) { + continue; + } + + l = &d->leases[e->lease_idx]; + + if (smb2_lease_equal(fsp_client_guid(fsp), + &fsp->lease->lease.lease_key, + &l->client_guid, + &l->lease_key)) { + continue; + } + + if (share_mode_stale_pid(d, i)) { + continue; + } + + if (!(e_lease_type & SMB2_LEASE_HANDLE)) { + continue; + } + + delay = true; + break_to = (e_lease_type & ~SMB2_LEASE_HANDLE); + + send_break_message(fsp->conn->sconn->msg_ctx, e, break_to); + } + + if (!delay) { + return NULL; + } + + /* Setup a watch on this record. */ + rename_state = talloc_zero(req, struct defer_rename_state); + if (rename_state == NULL) { + return NULL; + } + + rename_state->req = req; + rename_state->smb2req = smb2req; + rename_state->ev = ev; + rename_state->fsp = fsp; + rename_state->data = data; + rename_state->data_size = data_size; + + subreq = dbwrap_record_watch_send( + rename_state, + ev, + lck->data->record, + fsp->conn->sconn->msg_ctx); + + if (subreq == NULL) { + exit_server("Could not watch share mode record for rename\n"); + } + + tevent_req_set_callback(subreq, defer_rename_done, rename_state); + + timeout = timeval_set(OPLOCK_BREAK_TIMEOUT*2, 0); + if (!tevent_req_set_endtime(subreq, + ev, + timeval_sum(&smb2req->request_time, &timeout))) { + exit_server("Could not set rename timeout\n"); + } + + return subreq; +} + +static void defer_rename_done(struct tevent_req *subreq) +{ + struct defer_rename_state *state = tevent_req_callback_data( + subreq, struct defer_rename_state); + NTSTATUS status; + struct share_mode_lock *lck; + int ret_size = 0; + + status = dbwrap_record_watch_recv(subreq, state->req, NULL); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(5, ("dbwrap_record_watch_recv returned %s\n", + nt_errstr(status))); + tevent_req_nterror(state->req, status); + return; + } + + /* Do we still need to wait ? */ + lck = get_existing_share_mode_lock(state->req, state->fsp->file_id); + if (lck == NULL) { + tevent_req_nterror(state->req, NT_STATUS_UNSUCCESSFUL); + return; + } + subreq = delay_rename_for_lease_break(state->req, + state->smb2req, + state->ev, + state->fsp, + lck, + state->data, + state->data_size); + if (subreq) { + /* Yep - keep waiting. */ + TALLOC_FREE(state); + TALLOC_FREE(lck); + return; + } + + /* Do the rename under the lock. */ + status = smbd_do_setfilepathinfo(state->fsp->conn, + state->smb2req->smb1req, + state, + SMB2_FILE_RENAME_INFORMATION_INTERNAL, + state->fsp, + state->fsp->fsp_name, + &state->data, + state->data_size, + &ret_size); + + TALLOC_FREE(lck); + SAFE_FREE(state->data); + + if (!NT_STATUS_IS_OK(status)) { + tevent_req_nterror(state->req, status); + return; + } + + tevent_req_done(state->req); +} + struct smbd_smb2_setinfo_state { struct smbd_smb2_request *smb2req; }; @@ -173,6 +338,7 @@ static struct tevent_req *smbd_smb2_setinfo_send(TALLOC_CTX *mem_ctx, struct smbd_smb2_setinfo_state *state = NULL; struct smb_request *smbreq = NULL; connection_struct *conn = smb2req->tcon->compat; + struct share_mode_lock *lck = NULL; NTSTATUS status; req = tevent_req_create(mem_ctx, &state, @@ -284,6 +450,39 @@ static struct tevent_req *smbd_smb2_setinfo_send(TALLOC_CTX *mem_ctx, memcpy(data, in_input_buffer.data, data_size); } + if (file_info_level == SMB2_FILE_RENAME_INFORMATION_INTERNAL) { + struct tevent_req *subreq; + + lck = get_existing_share_mode_lock(mem_ctx, + fsp->file_id); + if (lck == NULL) { + tevent_req_nterror(req, + NT_STATUS_UNSUCCESSFUL); + return tevent_req_post(req, ev); + } + + subreq = delay_rename_for_lease_break(req, + smb2req, + ev, + fsp, + lck, + data, + data_size); + if (subreq) { + /* Wait for lease break response. */ + + /* Ensure we can't be closed in flight. */ + if (!aio_add_req_to_fsp(fsp, req)) { + TALLOC_FREE(lck); + tevent_req_nterror(req, NT_STATUS_NO_MEMORY); + return tevent_req_post(req, ev); + } + + TALLOC_FREE(lck); + return req; + } + } + status = smbd_do_setfilepathinfo(conn, smbreq, state, file_info_level, fsp, @@ -291,6 +490,7 @@ static struct tevent_req *smbd_smb2_setinfo_send(TALLOC_CTX *mem_ctx, &data, data_size, &ret_size); + TALLOC_FREE(lck); SAFE_FREE(data); if (!NT_STATUS_IS_OK(status)) { if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_LEVEL)) { -- 1.9.1 From 0ccee121a7f85b356c7eb430cbe748d13a3ae810 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Thu, 4 Dec 2014 21:29:47 -0800 Subject: [PATCH 6/6] s4: torture: leases. Simple lease_v2 rename test "v2_rename". Proves that renaming files can break handle leases. With the previous patches we now pass this. Signed-off-by: Jeremy Allison --- source4/torture/smb2/lease.c | 120 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/source4/torture/smb2/lease.c b/source4/torture/smb2/lease.c index 9d14aeb..282104c 100644 --- a/source4/torture/smb2/lease.c +++ b/source4/torture/smb2/lease.c @@ -3483,6 +3483,125 @@ static bool test_lease_timeout(struct torture_context *tctx, return ret; } +static bool test_lease_v2_rename(struct torture_context *tctx, + struct smb2_tree *tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_create io; + struct smb2_lease ls1; + struct smb2_lease ls2; + struct smb2_handle h, h1; + union smb_setfileinfo sinfo; + const char *fname = "lease_v2_rename_src.dat"; + const char *fname_dst = "lease_v2_rename_dst.dat"; + bool ret = true; + NTSTATUS status; + uint32_t caps; + enum protocol_types protocol; + + caps = smb2cli_conn_server_capabilities(tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + protocol = smbXcli_conn_protocol(tree->session->transport->conn); + if (protocol < PROTOCOL_SMB3_00) { + torture_skip(tctx, "v2 leases are not supported"); + } + + smb2_util_unlink(tree, fname); + smb2_util_unlink(tree, fname_dst); + + tree->session->transport->lease.handler = torture_lease_handler; + tree->session->transport->lease.private_data = tree; + tree->session->transport->oplock.handler = torture_oplock_handler; + tree->session->transport->oplock.private_data = tree; + + ZERO_STRUCT(break_info); + + ZERO_STRUCT(io); + smb2_lease_v2_create_share(&io, &ls1, false, fname, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state("RHW"), + 0x4711); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h = io.out.file.handle; + CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE); + ls1.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RHW", true, LEASE1, 0, 0, ls1.lease_epoch); + + /* Now rename - what happens ? */ + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.new_name = fname_dst; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + /* No lease break. */ + CHECK_NO_BREAK(tctx); + + /* Check we can open another handle on the new name. */ + smb2_lease_v2_create_share(&io, &ls1, false, fname_dst, + smb2_util_share_access("RWD"), + LEASE1, NULL, + smb2_util_lease_state(""), + ls1.lease_epoch); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + CHECK_LEASE_V2(&io, "RHW", true, LEASE1, 0, 0, ls1.lease_epoch); + smb2_util_close(tree, h1); + + /* Try another lease key. */ + smb2_lease_v2_create_share(&io, &ls2, false, fname_dst, + smb2_util_share_access("RWD"), + LEASE2, NULL, + smb2_util_lease_state("RWH"), + 0x44); + status = smb2_create(tree, mem_ctx, &io); + CHECK_STATUS(status, NT_STATUS_OK); + h1 = io.out.file.handle; + CHECK_CREATED(&io, EXISTED, FILE_ATTRIBUTE_ARCHIVE); + ls2.lease_epoch += 1; + CHECK_LEASE_V2(&io, "RH", true, LEASE2, 0, 0, ls2.lease_epoch ); + CHECK_BREAK_INFO_V2(tree->session->transport, + "RWH", "RH", LEASE1, ls1.lease_epoch + 1); + ls1.lease_epoch += 1; + ZERO_STRUCT(break_info); + + /* Now rename back. */ + ZERO_STRUCT(sinfo); + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = h; + sinfo.rename_information.in.overwrite = true; + sinfo.rename_information.in.new_name = fname; + status = smb2_setinfo_file(tree, &sinfo); + CHECK_STATUS(status, NT_STATUS_OK); + + /* Breaks to R on LEASE2. */ + CHECK_BREAK_INFO_V2(tree->session->transport, + "RH", "R", LEASE2, ls2.lease_epoch + 1); + ls1.lease_epoch += 1; + +done: + + smb2_util_close(tree, h); + smb2_util_close(tree, h1); + + smb2_util_unlink(tree, fname); + smb2_util_unlink(tree, fname_dst); + + smb2_util_unlink(tree, fname); + talloc_free(mem_ctx); + return ret; +} + + static bool test_lease_dynamic_share(struct torture_context *tctx, struct smb2_tree *tree1a) { @@ -3704,6 +3823,7 @@ struct torture_suite *torture_smb2_lease_init(void) torture_suite_add_1smb2_test(suite, "v2_epoch3", test_lease_v2_epoch3); torture_suite_add_1smb2_test(suite, "v2_complex1", test_lease_v2_complex1); torture_suite_add_1smb2_test(suite, "v2_complex2", test_lease_v2_complex2); + torture_suite_add_1smb2_test(suite, "v2_rename", test_lease_v2_rename); torture_suite_add_1smb2_test(suite, "dynamic_share", test_lease_dynamic_share); torture_suite_add_1smb2_test(suite, "timeout", test_lease_timeout); -- 1.9.1