s4: torture: Add an async SMB2_OP_FLUSH + SMB2_OP_CLOSE test to smb2.compound_async.
authorJeremy Allison <jra@samba.org>
Tue, 18 Oct 2022 23:22:33 +0000 (16:22 -0700)
committerRalph Boehme <slow@samba.org>
Thu, 17 Nov 2022 04:58:28 +0000 (04:58 +0000)
Shows we fail sending an SMB2_OP_FLUSH + SMB2_OP_CLOSE
compound. Internally the flush goes async and
we free the req, then we process the close.
When the flush completes it tries to access
already freed data.

Found using the Apple MacOSX client at SNIA SDC 2022.

Add knownfail.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=15172

Signed-off-by: Jeremy Allison <jra@samba.org>
Reviewed-by: Ralph Boehme <slow@samba.org>
selftest/knownfail.d/compound_async [new file with mode: 0644]
source3/selftest/tests.py
source4/torture/smb2/compound.c
source4/torture/smb2/smb2.c

diff --git a/selftest/knownfail.d/compound_async b/selftest/knownfail.d/compound_async
new file mode 100644 (file)
index 0000000..c18465b
--- /dev/null
@@ -0,0 +1 @@
+^samba3.smb2.compound_async.flush_close\(fileserver\)
index 67ba7b104844a28b8ff01016d1cb985f8d690642..f6cc6e0c639fc96e0b5372772de820d452f31315 100755 (executable)
@@ -1169,6 +1169,8 @@ for t in tests:
         plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
         plansmbtorture4testsuite(t, "nt4_dc", '//$SERVER_IP/aio -U$USERNAME%$PASSWORD', 'aio')
         plansmbtorture4testsuite(t, "ad_dc", '//$SERVER/tmp -U$USERNAME%$PASSWORD')
+    elif t == "smb2.compound_async":
+        plansmbtorture4testsuite(t, "fileserver", '//$SERVER_IP/tmp -U$USERNAME%$PASSWORD')
     elif t == "smb2.ea":
         plansmbtorture4testsuite(t, "fileserver", '//$SERVER/ea_acl_xattr --option=torture:acl_xattr_name=hackme -U$USERNAME%$PASSWORD')
     elif t == "rpc.samba3.netlogon" or t == "rpc.samba3.sessionkey":
index cf19361130f50fd3dad9588083be4ab4067dbc96..e78d78e3a985fd1f53ff4f0283159d6718bfe25d 100644 (file)
@@ -2057,6 +2057,110 @@ done:
        return ret;
 }
 
+static bool test_compound_async_flush_close(struct torture_context *tctx,
+                                           struct smb2_tree *tree)
+{
+       struct smb2_handle fhandle = { .data = { 0, 0 } };
+       struct smb2_handle relhandle = { .data = { UINT64_MAX, UINT64_MAX } };
+       struct smb2_close cl;
+       struct smb2_flush fl;
+       const char *fname = "compound_async_flush_close";
+       struct smb2_request *req[2];
+       NTSTATUS status;
+       bool ret = false;
+
+       /* Start clean. */
+       smb2_util_unlink(tree, fname);
+
+       /* Create a file. */
+       status = torture_smb2_testfile_access(tree,
+                                             fname,
+                                             &fhandle,
+                                             SEC_RIGHTS_FILE_ALL);
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       /* Now do a compound flush + close handle. */
+       smb2_transport_compound_start(tree->session->transport, 2);
+
+       ZERO_STRUCT(fl);
+       fl.in.file.handle = fhandle;
+
+       req[0] = smb2_flush_send(tree, &fl);
+       torture_assert_not_null_goto(tctx, req[0], ret, done,
+               "smb2_flush_send failed\n");
+
+       smb2_transport_compound_set_related(tree->session->transport, true);
+
+       ZERO_STRUCT(cl);
+       cl.in.file.handle = relhandle;
+       req[1] = smb2_close_send(tree, &cl);
+       torture_assert_not_null_goto(tctx, req[1], ret, done,
+               "smb2_close_send failed\n");
+
+       status = smb2_flush_recv(req[0], &fl);
+       /*
+        * On Windows, this flush will usually
+        * succeed as we have nothing to flush,
+        * so allow NT_STATUS_OK. Once bug #15172
+        * is fixed Samba will do the flush synchronously
+        * so allow NT_STATUS_OK.
+        */
+       if (!NT_STATUS_IS_OK(status)) {
+               /*
+                * If we didn't get NT_STATUS_OK, we *must*
+                * get NT_STATUS_INTERNAL_ERROR if the flush
+                * goes async.
+                *
+                * For pre-bugfix #15172 Samba, the flush goes async and
+                * we should get NT_STATUS_INTERNAL_ERROR.
+                */
+               torture_assert_ntstatus_equal_goto(tctx,
+                       status,
+                       NT_STATUS_INTERNAL_ERROR,
+                       ret,
+                       done,
+                       "smb2_flush_recv didn't return "
+                       "NT_STATUS_INTERNAL_ERROR.\n");
+       }
+       status = smb2_close_recv(req[1], &cl);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                       "smb2_close_recv failed.");
+
+       ZERO_STRUCT(fhandle);
+
+       /*
+        * Do several more operations on the tree, spaced
+        * out by 1 sec sleeps to make sure the server didn't
+        * crash on the close. The sleeps are required to
+        * make test test for a crash reliable, as we ensure
+        * the pthread fsync internally finishes and accesses
+        * freed memory. Without them the test occassionally
+        * passes as we disconnect before the pthread fsync
+        * finishes.
+        */
+       status = smb2_util_unlink(tree, fname);
+       CHECK_STATUS(status, NT_STATUS_OK);
+
+       sleep(1);
+       status = smb2_util_unlink(tree, fname);
+       CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+
+       sleep(1);
+       status = smb2_util_unlink(tree, fname);
+       CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);
+
+       ret = true;
+
+  done:
+
+       if (fhandle.data[0] != 0) {
+               smb2_util_close(tree, fhandle);
+       }
+
+       smb2_util_unlink(tree, fname);
+       return ret;
+}
+
 struct torture_suite *torture_smb2_compound_init(TALLOC_CTX *ctx)
 {
        struct torture_suite *suite = torture_suite_create(ctx, "compound");
@@ -2107,3 +2211,16 @@ struct torture_suite *torture_smb2_compound_find_init(TALLOC_CTX *ctx)
 
        return suite;
 }
+
+struct torture_suite *torture_smb2_compound_async_init(TALLOC_CTX *ctx)
+{
+       struct torture_suite *suite = torture_suite_create(ctx,
+                                       "compound_async");
+
+       torture_suite_add_1smb2_test(suite, "flush_close",
+               test_compound_async_flush_close);
+
+       suite->description = talloc_strdup(suite, "SMB2-COMPOUND-ASYNC tests");
+
+       return suite;
+}
index d7476ec6b8953a9ca595734c0cdd1a7fd2835a2a..911cd0b53527ea1e3f8cff028bbf67d8619d9f1f 100644 (file)
@@ -174,6 +174,7 @@ NTSTATUS torture_smb2_init(TALLOC_CTX *ctx)
        torture_suite_add_suite(suite, torture_smb2_lease_init(suite));
        torture_suite_add_suite(suite, torture_smb2_compound_init(suite));
        torture_suite_add_suite(suite, torture_smb2_compound_find_init(suite));
+       torture_suite_add_suite(suite, torture_smb2_compound_async_init(suite));
        torture_suite_add_suite(suite, torture_smb2_oplocks_init(suite));
        torture_suite_add_suite(suite, torture_smb2_kernel_oplocks_init(suite));
        torture_suite_add_suite(suite, torture_smb2_streams_init(suite));