]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
smbtorture: verify Windows SEC_FILE_APPEND_DATA behaviour
authorRalph Boehme <slow@samba.org>
Sat, 9 Nov 2024 15:00:39 +0000 (16:00 +0100)
committerJeremy Allison <jra@samba.org>
Tue, 7 Jan 2025 22:04:32 +0000 (22:04 +0000)
This test proves that Windows will not enforce append behaviour when using
SEC_FILE_APPEND_DATA: writing with an offset and length into existing data is
not mapped to an append operation. According to

https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntcreatefile

Windows internal APIs seemt to support append-IO:

  If only the FILE_APPEND_DATA and SYNCHRONIZE flags are set, the caller can
  write only to the end of the file, and any offset information on writes to the
  file is ignored. However, the file is automatically extended as necessary for
  this type of write operation.

...but at least over SMB2 it doesn't work.

This also demonstrates that Windows behaves as documented in the footnote in

MS-SMB2 3.3.5.13 "Receiving an SMB2 WRITE Request":

  If the range being written to is within the existing file size and
  Open.GrantedAccess does not include FILE_WRITE_DATA, or if the range being
  written to extends the file size and Open.GrantedAccess does not include
  FILE_APPEND_DATA, the server SHOULD<358> fail the request with
  STATUS_ACCESS_DENIED.

MS-SMB2 <358> Section 3.3.5.13:

  Windows SMB2 servers allow the operation when either FILE_APPEND_DATA or
  FILE_WRITE_DATA is set in Open.GrantedAccess.

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

Signed-off-by: Ralph Boehme <slow@samba.org>
source4/torture/smb2/read_write.c

index 707a49b6b1c4e2ae65d6de9040cc321251e28a3c..5b8ed4222d2d106e5a27b21898535a5f10ea6764 100644 (file)
@@ -347,6 +347,135 @@ done:
        return ret;
 }
 
+static bool test_append(struct torture_context *tctx,
+                       struct smb2_tree *tree)
+{
+       struct smb2_handle h = {};
+       uint8_t buf[1] = {};
+       struct smb2_create c = {};
+       union smb_fileinfo finfo = {};
+       off_t off;
+       uint64_t expected_size = 0;
+       NTSTATUS status;
+       bool ret = true;
+
+       smb2_util_unlink(tree, FNAME);
+
+       torture_comment(tctx, "Create file with 1 byte\n");
+
+       status = torture_smb2_testfile(tree, FNAME, &h);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+
+       expected_size = 0;
+
+       status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf));
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+       expected_size += ARRAY_SIZE(buf);
+
+       status = smb2_util_close(tree, h);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+       ZERO_STRUCT(h);
+
+       torture_comment(tctx, "Open file with SEC_FILE_APPEND_DATA"
+                       "|SEC_STD_SYNCHRONIZE\n");
+
+       c.in.desired_access = SEC_FILE_APPEND_DATA | SEC_STD_SYNCHRONIZE;
+       c.in.create_disposition = NTCREATEX_DISP_OPEN;
+       c.in.share_access =
+               NTCREATEX_SHARE_ACCESS_DELETE|
+               NTCREATEX_SHARE_ACCESS_READ|
+               NTCREATEX_SHARE_ACCESS_WRITE;
+       c.in.fname = FNAME;
+
+       status = smb2_create(tree, tree, &c);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+
+       h = c.out.file.handle;
+
+       torture_comment(tctx, "Write one byte at offset 0 and verify "
+                       "written data was not appended but written at "
+                       "requested offset\n");
+
+       status = smb2_util_write(tree, h, buf, 0, ARRAY_SIZE(buf));
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+
+       status = smb2_util_close(tree, h);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+       ZERO_STRUCT(h);
+
+       status = torture_smb2_testfile(tree, FNAME, &h);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+
+       finfo.generic.level = RAW_FILEINFO_ALL_INFORMATION;
+       finfo.generic.in.file.handle = h;
+       status = smb2_getinfo_file(tree, tree, &finfo);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+
+       torture_assert_u64_equal_goto(tctx, finfo.all_info.out.size,
+                                     expected_size,
+                                     ret, done, "wrong size");
+
+       status = smb2_util_close(tree, h);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+       ZERO_STRUCT(h);
+
+       torture_comment(tctx, "Open file in write-data only mode "
+                       "and check if we can append\n");
+
+       c.in.desired_access = SEC_FILE_WRITE_DATA | SEC_FILE_READ_ATTRIBUTE;
+       c.in.create_disposition = NTCREATEX_DISP_OPEN;
+       c.in.share_access =
+               NTCREATEX_SHARE_ACCESS_DELETE|
+               NTCREATEX_SHARE_ACCESS_READ|
+               NTCREATEX_SHARE_ACCESS_WRITE;
+       c.in.fname = FNAME;
+
+       status = smb2_create(tree, tree, &c);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+       h = c.out.file.handle;
+
+       off = 1000;
+
+       status = smb2_util_write(tree, h, buf, off, ARRAY_SIZE(buf));
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+
+       expected_size = off + ARRAY_SIZE(buf);
+
+       finfo.generic.level = RAW_FILEINFO_ALL_INFORMATION;
+       finfo.generic.in.file.handle = h;
+       status = smb2_getinfo_file(tree, tree, &finfo);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+
+       torture_assert_u64_equal_goto(tctx, finfo.all_info.out.size,
+                                     expected_size,
+                                     ret, done, "wrong size");
+
+       status = smb2_util_close(tree, h);
+       torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+                                       "Incorrect status\n");
+       ZERO_STRUCT(h);
+
+done:
+       if (!smb2_util_handle_empty(h)) {
+               smb2_util_close(tree, h);
+       }
+       smb2_util_unlink(tree, FNAME);
+       return ret;
+}
+
+
 struct torture_suite *torture_smb2_readwrite_init(TALLOC_CTX *ctx)
 {
        struct torture_suite *suite = torture_suite_create(ctx, "rw");
@@ -354,6 +483,7 @@ struct torture_suite *torture_smb2_readwrite_init(TALLOC_CTX *ctx)
        torture_suite_add_2smb2_test(suite, "rw1", run_smb2_readwritetest);
        torture_suite_add_2smb2_test(suite, "rw2", run_smb2_wrap_readwritetest);
        torture_suite_add_1smb2_test(suite, "invalid", test_rw_invalid);
+       torture_suite_add_1smb2_test(suite, "append", test_append);
 
        suite->description = talloc_strdup(suite, "SMB2 Samba4 Read/Write");