From: Ralph Boehme Date: Wed, 2 Oct 2024 05:47:25 +0000 (+0200) Subject: smbtorture: add a test for recursive h-lease break when renaming X-Git-Tag: tdb-1.4.13~735 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5305e361942c0893143abf74fd832c71fb844643;p=thirdparty%2Fsamba.git smbtorture: add a test for recursive h-lease break when renaming BUG: https://bugzilla.samba.org/show_bug.cgi?id=15608 Signed-off-by: Ralph Boehme Reviewed-by: Stefan Metzmacher --- diff --git a/selftest/knownfail.d/samba3.smb2.lease b/selftest/knownfail.d/samba3.smb2.lease new file mode 100644 index 00000000000..499570c94bb --- /dev/null +++ b/selftest/knownfail.d/samba3.smb2.lease @@ -0,0 +1 @@ +^samba3.smb2.lease.rename_dir_openfile\(fileserver\) diff --git a/source4/torture/smb2/lease.c b/source4/torture/smb2/lease.c index 0bebbef4a4c..76a946c87de 100644 --- a/source4/torture/smb2/lease.c +++ b/source4/torture/smb2/lease.c @@ -5066,6 +5066,342 @@ done: return ret; } +struct rename_tcase_open { + bool hlease; + bool close_on_break; +}; + +struct rename_tcase { + const char *name; + bool disabled; + struct rename_tcase_open o1; + struct rename_tcase_open o2; + bool do_o3; + struct rename_tcase_open o3; + NTSTATUS status; +}; + +static bool torture_rename_dir_openfile_do(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2, + struct rename_tcase *t) +{ + struct smb2_create c = {}; + union smb_setfileinfo sinfo = {}; + struct smb2_handle d1 = {}; + struct smb2_handle h1 = {}; + struct smb2_handle h2 = {}; + struct smb2_handle h3 = {}; + struct smb2_handle *h = NULL; + struct smb2_lease *please1 = NULL; + struct smb2_lease *please2 = NULL; + struct smb2_lease *please3 = NULL; + struct smb2_lease lease1 = {}; + struct smb2_lease lease2 = {}; + struct smb2_lease lease3 = {}; + struct smb2_request *req = NULL; + struct smb2_lease_break_ack ack = {}; + struct rename_tcase_open *to = NULL; + const char *dname = "torture_rename_dir_openfile_dir"; + const char *fname1 = "torture_rename_dir_openfile_dir\\torture_rename_dir_openfile_file1"; + const char *fname2 = "torture_rename_dir_openfile_dir\\torture_rename_dir_openfile_file2"; + const char *fname3 = "torture_rename_dir_openfile_dir\\torture_rename_dir_openfile_file3"; + const char *new_dname = "torture_rename_dir_openfile_dir-renamed"; + bool expect_immediate_fail = false; + bool ret = true; + NTSTATUS status; + + torture_comment(tctx, "Subtest: %s\n", t->name); + if (t->disabled) { + torture_comment(tctx, "...skipped\n"); + return true; + } + + tree2->session->transport->lease.handler = torture_lease_handler; + tree2->session->transport->lease.private_data = tree2; + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + smb2_deltree(tree1, dname); + smb2_deltree(tree1, new_dname); + + torture_comment(tctx, "Creating base directory\n"); + + smb2_lease_v2_create_share(&c, NULL, true, dname, + smb2_util_share_access("RWD"), + 0, + NULL, + smb2_util_lease_state(""), + 0); + status = smb2_create(tree1, tree1, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + d1 = c.out.file.handle; + + torture_comment(tctx, "Creating test file1\n"); + + if (t->o1.hlease) { + please1 = &lease1; + } + smb2_lease_v2_create_share(&c, please1, false, fname1, + smb2_util_share_access("RWD"), + LEASE1, + NULL, + smb2_util_lease_state("RH"), + 0); + status = smb2_create(tree2, tree2, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = c.out.file.handle; + + torture_comment(tctx, "Creating test file2\n"); + + if (t->o2.hlease) { + please2 = &lease2; + } + smb2_lease_v2_create_share(&c, please2, false, fname2, + smb2_util_share_access("RWD"), + LEASE2, + NULL, + smb2_util_lease_state("RH"), + 0); + status = smb2_create(tree2, tree2, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h2 = c.out.file.handle; + + torture_comment(tctx, "Renaming directory\n"); + + sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION; + sinfo.rename_information.in.file.handle = d1; + sinfo.rename_information.in.new_name = new_dname; + + req = smb2_setinfo_file_send(tree1, &sinfo); + torture_assert_goto(tctx, req != NULL, ret, done, + "smb2_setinfo_file_send"); + + torture_assert(tctx, req->state == SMB2_REQUEST_RECV, "bad req state"); + + if (t->o1.hlease || t->o2.hlease) { + /* Get the first break */ + torture_wait_for_lease_break(tctx); + + if (lease_break_info.count == 0) { + /* + * If one of the two opens was without a h-lease, the + * scan for opens might hit the open without h-lease + * first triggering an immediate STATUS_ACCESS_DENIED + * for the rename without sending out any lease break. + */ + torture_assert_goto(tctx, (!t->o1.hlease || !t->o2.hlease), + ret, done, + "Expected only one hlease when getting no hlease break\n"); + + status = smb2_setinfo_recv(req); + torture_assert_ntstatus_equal_goto(tctx, status, t->status, ret, done, + "Rename didn't work as expected\n"); + goto done; + } + + if (lease_break_info.lease_break.current_lease.lease_key.data[0] == LEASE1 && + lease_break_info.lease_break.current_lease.lease_key.data[1] == ~LEASE1) + { + torture_comment(tctx, "Got break for file 1\n"); + please1 = &lease1; + h = &h1; + to = &t->o1; + } else { + torture_comment(tctx, "Got break for file 2\n"); + please1 = &lease2; + h = &h2; + to = &t->o2; + } + please1->lease_epoch += 2; + + CHECK_BREAK_INFO_V2_NOWAIT(tree2->session->transport, + "RH", "R", + please1->lease_key.data[0], + please1->lease_epoch); + + ack.in.lease.lease_key = lease_break_info.lease_break.current_lease.lease_key; + ack.in.lease.lease_state = lease_break_info.lease_break.new_lease_state; + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + if (to->close_on_break) { + status = smb2_util_close(tree2, *h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCTP(h); + } else { + status = smb2_lease_break_ack(tree2, &ack); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "ack failed\n"); + expect_immediate_fail = true; + } + } + + if (t->do_o3) { + torture_comment(tctx, "Doing additional open after first break\n"); + + if (t->o3.hlease) { + please3 = &lease3; + } + smb2_lease_v2_create_share(&c, please3, false, fname3, + smb2_util_share_access("RWD"), + LEASE3, + NULL, + smb2_util_lease_state("RH"), + 0); + status = smb2_create(tree2, tree2, &c); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h3 = c.out.file.handle; + } + + if (!expect_immediate_fail && t->o1.hlease && t->o2.hlease) { + /* Get the second break */ + torture_wait_for_lease_break(tctx); + + if (lease_break_info.lease_break.current_lease.lease_key.data[0] == LEASE1 && + lease_break_info.lease_break.current_lease.lease_key.data[1] == ~LEASE1) + { + torture_comment(tctx, "Got break for file 1\n"); + please1 = &lease1; + h = &h1; + to = &t->o1; + } else { + torture_comment(tctx, "Got break for file 2\n"); + please1 = &lease2; + h = &h2; + to = &t->o2; + } + please1->lease_epoch += 2; + + CHECK_BREAK_INFO_V2_NOWAIT(tree2->session->transport, + "RH", "R", + please1->lease_key.data[0], + please1->lease_epoch); + + ack.in.lease.lease_key = lease_break_info.lease_break.current_lease.lease_key; + ack.in.lease.lease_state = lease_break_info.lease_break.new_lease_state; + torture_reset_lease_break_info(tctx, &lease_break_info); + lease_break_info.lease_skip_ack = true; + + if (to->close_on_break) { + status = smb2_util_close(tree2, *h); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCTP(h); + } else { + status = smb2_lease_break_ack(tree2, &ack); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "ack failed\n"); + } + } + + status = smb2_setinfo_recv(req); + torture_assert_ntstatus_equal_goto(tctx, status, t->status, ret, done, + "Rename didn't work as expected\n"); + +done: + if (!smb2_util_handle_empty(d1)) { + smb2_util_close(tree1, d1); + } + if (!smb2_util_handle_empty(h1)) { + smb2_util_close(tree2, h1); + } + if (!smb2_util_handle_empty(h2)) { + smb2_util_close(tree2, h2); + } + if (!smb2_util_handle_empty(h3)) { + smb2_util_close(tree2, h3); + } + smb2_deltree(tree1, dname); + smb2_deltree(tree1, new_dname); + return ret; +} + +static bool torture_rename_dir_openfile(struct torture_context *tctx, + struct smb2_tree *tree1, + struct smb2_tree *tree2) +{ + struct rename_tcase tcases[] = { + { + .name = "two-hleases-two-closes", + .o1 = { .hlease = true, .close_on_break = true }, + .o2 = { .hlease = true, .close_on_break = true }, + .do_o3 = false, + .status = NT_STATUS_OK, + }, + { + .name = "no-hleases", + .o1 = { .hlease = false, }, + .o2 = { .hlease = false, }, + .do_o3 = false, + .status = NT_STATUS_ACCESS_DENIED, + }, + { + .name = "two-hleases-second-hlease-close", + .o1 = { .hlease = true, .close_on_break = false }, + .o2 = { .hlease = true, .close_on_break = true }, + .do_o3 = false, + .status = NT_STATUS_ACCESS_DENIED, + }, + { + .name = "two-hleases-first-hlease-close", + .o1 = { .hlease = true, .close_on_break = true }, + .o2 = { .hlease = true, .close_on_break = false }, + .do_o3 = false, + .status = NT_STATUS_ACCESS_DENIED, + }, + { + .name = "first-hlease-close", + .o1 = { .hlease = true, .close_on_break = true }, + .o2 = { .hlease = false, }, + .do_o3 = false, + .status = NT_STATUS_ACCESS_DENIED, + }, + { + .name = "second-hlease-close", + .o1 = { .hlease = false, }, + .o2 = { .hlease = true, .close_on_break = true }, + .do_o3 = false, + .status = NT_STATUS_ACCESS_DENIED, + }, + { + .name = "two-hleases-two-closes-addopen-w-hlease", + .o1 = { .hlease = true, .close_on_break = true }, + .o2 = { .hlease = true, .close_on_break = true }, + .do_o3 = true, + .o3 = { .hlease = true, .close_on_break = true }, + .status = NT_STATUS_ACCESS_DENIED, + }, + { + .name = "two-hleases-two-closes-addopen-wo-hlease", + .o1 = { .hlease = true, .close_on_break = true }, + .o2 = { .hlease = true, .close_on_break = true }, + .do_o3 = true, + .o3 = { .hlease = false, }, + .status = NT_STATUS_ACCESS_DENIED, + }, + }; + size_t i; + bool ret; + + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree2; + torture_reset_lease_break_info(tctx, &lease_break_info); + + for (i = 0; i < ARRAY_SIZE(tcases); i++) { + ret = torture_rename_dir_openfile_do(tctx, tree1, tree2, &tcases[i]); + torture_assert_goto(tctx, ret, ret, done, "test failed\n"); + } + +done: + return ret; +} + struct torture_suite *torture_smb2_lease_init(TALLOC_CTX *ctx) { struct torture_suite *suite = @@ -5126,6 +5462,8 @@ struct torture_suite *torture_smb2_lease_init(TALLOC_CTX *ctx) test_initial_delete_logoff); torture_suite_add_1smb2_test(suite, "initial_delete_disconnect", test_initial_delete_disconnect); + torture_suite_add_2smb2_test(suite, "rename_dir_openfile", + torture_rename_dir_openfile); suite->description = talloc_strdup(suite, "SMB2-LEASE tests"); diff --git a/source4/torture/smb2/lease_break_handler.h b/source4/torture/smb2/lease_break_handler.h index 90fde1a9217..f839bcaeaa3 100644 --- a/source4/torture/smb2/lease_break_handler.h +++ b/source4/torture/smb2/lease_break_handler.h @@ -100,6 +100,22 @@ struct lease_break_info { } \ } while(0) +#define _CHECK_BREAK_INFO_NOWAIT(__oldstate, __state, __key) \ + do { \ + CHECK_VAL(lease_break_info.failures, 0); \ + CHECK_VAL(lease_break_info.count, 1); \ + CHECK_LEASE_BREAK(&lease_break_info.lease_break, (__oldstate), \ + (__state), (__key)); \ + if (!lease_break_info.lease_skip_ack && \ + (lease_break_info.lease_break.break_flags & \ + SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED)) \ + { \ + torture_wait_for_lease_break(tctx); \ + CHECK_LEASE_BREAK_ACK(&lease_break_info.lease_break_ack, \ + (__state), (__key)); \ + } \ + } while(0) + #define CHECK_BREAK_INFO(__oldstate, __state, __key) \ do { \ _CHECK_BREAK_INFO(__oldstate, __state, __key); \ @@ -116,6 +132,16 @@ struct lease_break_info { } \ } while(0) +#define CHECK_BREAK_INFO_V2_NOWAIT(__transport, __oldstate, __state, __key, __epoch) \ + do { \ + _CHECK_BREAK_INFO_NOWAIT(__oldstate, __state, __key); \ + CHECK_VAL(lease_break_info.lease_break.new_epoch, __epoch); \ + if (!TARGET_IS_SAMBA3(tctx)) { \ + CHECK_VAL((uintptr_t)lease_break_info.lease_transport, \ + (uintptr_t)__transport); \ + } \ + } while(0) + extern struct lease_break_info lease_break_info; bool torture_lease_handler(struct smb2_transport *transport,