[PATCH 14/17] torture: copychunk test suite improvements

David Disseldorp ddiss at samba.org
Tue Jan 15 09:23:09 MST 2013


Allow for large files in test_setup_copy_chunk():
  Write test data in 1M IOs, rather than attempting to do the whole
  thing in one go.

Add copychunk bad resume key test:
  Send a copy chunk request with an intentionally bogus resume key
  (source key handle).

Add copychunk src=dest test:
  Test copychunk requests where the source and destination handles refer
  to the same file.

Add copychunk src=dest overlap test.

Add desired access args to test_setup_copy_chunk().

Add copychunk_bad_access test:
  Open the copychunk source and destination files with differing
  desired_access values. Confirm copychunk response matches 2k8 and 2k12
  behaviour.

Add copy_chunk_src_exceed test:
  Attempts to copy more data than is present in the copychunk source
  file.

Add copy_chunk_src_exceed_multi test:
  Test whether the first chunk in a multi-chunk copychunk request is
  written to disk, where the second chunk is invalid due to src file
  overrun.

Add copy_chunk_sparse_dest test:
  Issue a request where the target offset exceeds the file size, resulting
  in a sparse region.

Add copy_chunk_max_output_sz test.
---
 source4/torture/smb2/ioctl.c |  727 +++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 719 insertions(+), 8 deletions(-)

diff --git a/source4/torture/smb2/ioctl.c b/source4/torture/smb2/ioctl.c
index 4d2137e..501b233 100644
--- a/source4/torture/smb2/ioctl.c
+++ b/source4/torture/smb2/ioctl.c
@@ -153,12 +153,15 @@ static bool test_setup_copy_chunk(struct torture_context *torture,
 				  uint32_t nchunks,
 				  struct smb2_handle *src_h,
 				  uint64_t src_size,
+				  uint32_t src_desired_access,
 				  struct smb2_handle *dest_h,
 				  uint64_t dest_size,
+				  uint32_t dest_desired_access,
 				  struct srv_copychunk_copy *cc_copy,
 				  union smb_ioctl *ioctl)
 {
 	struct req_resume_key_rsp res_key;
+	struct smb2_create io;
 	NTSTATUS status;
 	enum ndr_err_code ndr_ret;
 	uint64_t i;
@@ -168,26 +171,66 @@ static bool test_setup_copy_chunk(struct torture_context *torture,
 	smb2_util_unlink(tree, FNAME);
 	smb2_util_unlink(tree, FNAME2);
 
-	status = torture_smb2_testfile(tree, FNAME, src_h);
-	torture_assert_ntstatus_ok(torture, status, "create write");
+	ZERO_STRUCT(io);
+	io.in.desired_access = src_desired_access;
+	io.in.file_attributes   = FILE_ATTRIBUTE_NORMAL;
+	io.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
+	io.in.share_access =
+		NTCREATEX_SHARE_ACCESS_DELETE|
+		NTCREATEX_SHARE_ACCESS_READ|
+		NTCREATEX_SHARE_ACCESS_WRITE;
+	io.in.fname = FNAME;
+
+	status = smb2_create(tree, mem_ctx, &io);
+	torture_assert_ntstatus_ok(torture, status, "src create");
+
+	*src_h = io.out.file.handle;
 
 	if (src_size > 0) {
+		uint64_t cur_off = 0;
 		for (i = 0; i <= src_size - 8; i += 8) {
 			SBVAL(buf, i, patt_hash(i));
 		}
-		status = smb2_util_write(tree, *src_h, buf, 0, src_size);
-		torture_assert_ntstatus_ok(torture, status, "src write");
+		while (src_size > 0) {
+			uint64_t io_sz = MIN(1024 * 1024, src_size);
+			status = smb2_util_write(tree, *src_h,
+						 buf + cur_off, cur_off, io_sz);
+			torture_assert_ntstatus_ok(torture, status, "src write");
+
+			src_size -= io_sz;
+			cur_off += io_sz;
+		}
 	}
 
-	status = torture_smb2_testfile(tree, FNAME2, dest_h);
-	torture_assert_ntstatus_ok(torture, status, "create write");
+	ZERO_STRUCT(io);
+	io.in.desired_access = dest_desired_access;
+	io.in.file_attributes   = FILE_ATTRIBUTE_NORMAL;
+	io.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
+	io.in.share_access =
+		NTCREATEX_SHARE_ACCESS_DELETE|
+		NTCREATEX_SHARE_ACCESS_READ|
+		NTCREATEX_SHARE_ACCESS_WRITE;
+	io.in.fname = FNAME2;
+
+	status = smb2_create(tree, mem_ctx, &io);
+	torture_assert_ntstatus_ok(torture, status, "dest create");
+
+	*dest_h = io.out.file.handle;
 
 	if (dest_size > 0) {
+		uint64_t cur_off = 0;
 		for (i = 0; i <= dest_size - 8; i += 8) {
 			SBVAL(buf, i, patt_hash(i));
 		}
-		status = smb2_util_write(tree, *dest_h, buf, 0, dest_size);
-		torture_assert_ntstatus_ok(torture, status, "dest write");
+		while (dest_size > 0) {
+			uint64_t io_sz = MIN(1024 * 1024, dest_size);
+			status = smb2_util_write(tree, *dest_h,
+						 buf + cur_off, cur_off, io_sz);
+			torture_assert_ntstatus_ok(torture, status, "dest write");
+
+			dest_size -= io_sz;
+			cur_off += io_sz;
+		}
 	}
 
 	ZERO_STRUCTPN(ioctl);
@@ -257,7 +300,9 @@ static bool test_ioctl_copy_chunk_simple(struct torture_context *torture,
 	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
 				   1, /* 1 chunk */
 				   &src_h, 4096, /* fill 4096 byte src file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &dest_h, 0,	/* 0 byte dest file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &cc_copy,
 				   &ioctl);
 	if (!ok) {
@@ -319,7 +364,9 @@ static bool test_ioctl_copy_chunk_multi(struct torture_context *torture,
 	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
 				   2, /* chunks */
 				   &src_h, 8192, /* src file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &dest_h, 0,	/* dest file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &cc_copy,
 				   &ioctl);
 	if (!ok) {
@@ -380,7 +427,9 @@ static bool test_ioctl_copy_chunk_tiny(struct torture_context *torture,
 	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
 				   2, /* chunks */
 				   &src_h, 100, /* src file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &dest_h, 0,	/* dest file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &cc_copy,
 				   &ioctl);
 	if (!ok) {
@@ -446,7 +495,9 @@ static bool test_ioctl_copy_chunk_over(struct torture_context *torture,
 	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
 				   2, /* chunks */
 				   &src_h, 8192, /* src file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &dest_h, 4096, /* dest file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &cc_copy,
 				   &ioctl);
 	if (!ok) {
@@ -513,7 +564,9 @@ static bool test_ioctl_copy_chunk_append(struct torture_context *torture,
 	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
 				   2, /* chunks */
 				   &src_h, 4096, /* src file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &dest_h, 0,	/* dest file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &cc_copy,
 				   &ioctl);
 	if (!ok) {
@@ -584,7 +637,9 @@ static bool test_ioctl_copy_chunk_limits(struct torture_context *torture,
 	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
 				   1, /* chunks */
 				   &src_h, 4096, /* src file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &dest_h, 0,	/* dest file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &cc_copy,
 				   &ioctl);
 	if (!ok) {
@@ -643,7 +698,9 @@ static bool test_ioctl_copy_chunk_src_lck(struct torture_context *torture,
 	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
 				   1, /* chunks */
 				   &src_h, 4096, /* src file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &dest_h, 0,	/* dest file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &cc_copy,
 				   &ioctl);
 	if (!ok) {
@@ -769,7 +826,9 @@ static bool test_ioctl_copy_chunk_dest_lck(struct torture_context *torture,
 	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
 				   1, /* chunks */
 				   &src_h, 4096, /* src file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &dest_h, 4096,	/* dest file */
+				   SEC_RIGHTS_FILE_ALL,
 				   &cc_copy,
 				   &ioctl);
 	if (!ok) {
@@ -848,6 +907,642 @@ static bool test_ioctl_copy_chunk_dest_lck(struct torture_context *torture,
 	return true;
 }
 
+static bool test_ioctl_copy_chunk_bad_key(struct torture_context *torture,
+					  struct smb2_tree *tree)
+{
+	struct smb2_handle src_h;
+	struct smb2_handle dest_h;
+	NTSTATUS status;
+	union smb_ioctl ioctl;
+	TALLOC_CTX *tmp_ctx = talloc_new(tree);
+	struct srv_copychunk_copy cc_copy;
+	enum ndr_err_code ndr_ret;
+	bool ok;
+
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   1,
+				   &src_h, 4096,
+				   SEC_RIGHTS_FILE_ALL,
+				   &dest_h, 0,
+				   SEC_RIGHTS_FILE_ALL,
+				   &cc_copy,
+				   &ioctl);
+	if (!ok) {
+		torture_fail(torture, "setup copy chunk error");
+	}
+
+	/* overwrite the resume key with a bogus value */
+	memcpy(cc_copy.source_key, "deadbeefdeadbeefdeadbeef", 24);
+
+	cc_copy.chunks[0].source_off = 0;
+	cc_copy.chunks[0].target_off = 0;
+	cc_copy.chunks[0].length = 4096;
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	/* Server 2k12 returns NT_STATUS_OBJECT_NAME_NOT_FOUND */
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_equal(torture, status,
+				      NT_STATUS_OBJECT_NAME_NOT_FOUND,
+				      "FSCTL_SRV_COPYCHUNK");
+
+	smb2_util_close(tree, src_h);
+	smb2_util_close(tree, dest_h);
+	talloc_free(tmp_ctx);
+	return true;
+}
+
+static bool test_ioctl_copy_chunk_src_is_dest(struct torture_context *torture,
+					      struct smb2_tree *tree)
+{
+	struct smb2_handle src_h;
+	struct smb2_handle dest_h;
+	NTSTATUS status;
+	union smb_ioctl ioctl;
+	TALLOC_CTX *tmp_ctx = talloc_new(tree);
+	struct srv_copychunk_copy cc_copy;
+	struct srv_copychunk_rsp cc_rsp;
+	enum ndr_err_code ndr_ret;
+	bool ok;
+
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   1,
+				   &src_h, 8192,
+				   SEC_RIGHTS_FILE_ALL,
+				   &dest_h, 0,
+				   SEC_RIGHTS_FILE_ALL,
+				   &cc_copy,
+				   &ioctl);
+	if (!ok) {
+		torture_fail(torture, "setup copy chunk error");
+	}
+
+	/* the source is also the destination */
+	ioctl.smb2.in.file.handle = src_h;
+
+	/* non-overlapping */
+	cc_copy.chunks[0].source_off = 0;
+	cc_copy.chunks[0].target_off = 4096;
+	cc_copy.chunks[0].length = 4096;
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_ok(torture, status,
+				   "FSCTL_SRV_COPYCHUNK");
+
+	ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx,
+				       &cc_rsp,
+			(ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_pull_srv_copychunk_rsp");
+
+	ok = check_copy_chunk_rsp(torture, &cc_rsp,
+				  1,	/* chunks written */
+				  0,	/* chunk bytes unsuccessfully written */
+				  4096); /* total bytes written */
+	if (!ok) {
+		torture_fail(torture, "bad copy chunk response data");
+	}
+
+	ok = check_pattern(torture, tree, tmp_ctx, src_h, 0, 4096, 0);
+	if (!ok) {
+		torture_fail(torture, "inconsistent file data");
+	}
+	ok = check_pattern(torture, tree, tmp_ctx, src_h, 4096, 4096, 0);
+	if (!ok) {
+		torture_fail(torture, "inconsistent file data");
+	}
+
+	smb2_util_close(tree, src_h);
+	smb2_util_close(tree, dest_h);
+	talloc_free(tmp_ctx);
+	return true;
+}
+
+/*
+ * Test a single-chunk copychunk request, where the source and target ranges
+ * overlap, and the SourceKey refers to the same target file. E.g:
+ *
+ * Initial State
+ * -------------
+ * 	File:		src_and_dest
+ * 	Offset:		0123456789
+ * 	Data:		abcdefghij
+ *
+ * Request
+ * -------
+ * 	FSCTL_SRV_COPYCHUNK(src_and_dest)
+ * 	SourceKey = SRV_REQUEST_RESUME_KEY(src_and_dest)
+ * 	ChunkCount = 1
+ * 	Chunks[0].SourceOffset = 0
+ * 	Chunks[0].TargetOffset = 4
+ * 	Chunks[0].Length = 6
+ *
+ * Resultant State
+ * ---------------
+ * 	File:		src_and_dest
+ * 	Offset:		0123456789
+ * 	Data:		abcdabcdef
+ *
+ * The resultant contents of src_and_dest is dependent on the server's
+ * copy algorithm. In the above example, the server uses an IO buffer
+ * large enough to hold the entire six-byte source data before writing
+ * to TargetOffset. If the server were to use a four-byte IO buffer and
+ * started reads/writes from the lowest offset, then the two overlapping
+ * bytes in the above example would be overwritten before being read. The
+ * resultant file contents would be abcdabcdab.
+ *
+ * Windows 2008r2 appears to use a 2048 byte copy buffer, overlapping bytes
+ * after this offset are written before being read. Windows 2012 on the
+ * other hand appears to use a buffer large enough to hold its maximum
+ * supported chunk size (1M). Samba currently uses a 64k copy buffer by
+ * default (vfs_cc_state.buf).
+ *
+ * This test uses an 8-byte overlap at 2040-2048, so that it passes against
+ * Windows 2008, 2012 and Samba servers.
+ */
+static bool
+test_ioctl_copy_chunk_src_is_dest_overlap(struct torture_context *torture,
+					  struct smb2_tree *tree)
+{
+	struct smb2_handle src_h;
+	struct smb2_handle dest_h;
+	NTSTATUS status;
+	union smb_ioctl ioctl;
+	TALLOC_CTX *tmp_ctx = talloc_new(tree);
+	struct srv_copychunk_copy cc_copy;
+	struct srv_copychunk_rsp cc_rsp;
+	enum ndr_err_code ndr_ret;
+	bool ok;
+
+	/* exceed the vfs_default copy buffer */
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   1,
+				   &src_h, 2048 * 2,
+				   SEC_RIGHTS_FILE_ALL,
+				   &dest_h, 0,
+				   SEC_RIGHTS_FILE_ALL,
+				   &cc_copy,
+				   &ioctl);
+	if (!ok) {
+		torture_fail(torture, "setup copy chunk error");
+	}
+
+	/* the source is also the destination */
+	ioctl.smb2.in.file.handle = src_h;
+
+	/* 8 bytes overlap between source and target ranges */
+	cc_copy.chunks[0].source_off = 0;
+	cc_copy.chunks[0].target_off = 2048 - 8;
+	cc_copy.chunks[0].length = 2048;
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_ok(torture, status,
+				   "FSCTL_SRV_COPYCHUNK");
+
+	ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx,
+				       &cc_rsp,
+			(ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_pull_srv_copychunk_rsp");
+
+	ok = check_copy_chunk_rsp(torture, &cc_rsp,
+				  1,	/* chunks written */
+				  0,	/* chunk bytes unsuccessfully written */
+				  2048); /* total bytes written */
+	if (!ok) {
+		torture_fail(torture, "bad copy chunk response data");
+	}
+
+	ok = check_pattern(torture, tree, tmp_ctx, src_h, 0, 2048 - 8, 0);
+	if (!ok) {
+		torture_fail(torture, "inconsistent file data");
+	}
+	ok = check_pattern(torture, tree, tmp_ctx, src_h, 2048 - 8, 2048, 0);
+	if (!ok) {
+		torture_fail(torture, "inconsistent file data");
+	}
+
+	smb2_util_close(tree, src_h);
+	smb2_util_close(tree, dest_h);
+	talloc_free(tmp_ctx);
+	return true;
+}
+
+static bool test_ioctl_copy_chunk_bad_access(struct torture_context *torture,
+					     struct smb2_tree *tree)
+{
+	struct smb2_handle src_h;
+	struct smb2_handle dest_h;
+	NTSTATUS status;
+	union smb_ioctl ioctl;
+	TALLOC_CTX *tmp_ctx = talloc_new(tree);
+	struct srv_copychunk_copy cc_copy;
+	enum ndr_err_code ndr_ret;
+	bool ok;
+
+	/* no read permission on src */
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   1, /* 1 chunk */
+				   &src_h, 4096, /* fill 4096 byte src file */
+				   SEC_RIGHTS_FILE_WRITE,
+				   &dest_h, 0,	/* 0 byte dest file */
+				   SEC_RIGHTS_FILE_ALL,
+				   &cc_copy,
+				   &ioctl);
+	if (!ok) {
+		torture_fail(torture, "setup copy chunk error");
+	}
+
+	cc_copy.chunks[0].source_off = 0;
+	cc_copy.chunks[0].target_off = 0;
+	cc_copy.chunks[0].length = 4096;
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_equal(torture, status,
+				      NT_STATUS_ACCESS_DENIED,
+				      "FSCTL_SRV_COPYCHUNK");
+
+	smb2_util_close(tree, src_h);
+	smb2_util_close(tree, dest_h);
+
+	/* no write permission on dest */
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   1, /* 1 chunk */
+				   &src_h, 4096, /* fill 4096 byte src file */
+				   SEC_RIGHTS_FILE_ALL,
+				   &dest_h, 0,	/* 0 byte dest file */
+				   (SEC_RIGHTS_FILE_READ
+				    | SEC_RIGHTS_FILE_EXECUTE),
+				   &cc_copy,
+				   &ioctl);
+	if (!ok) {
+		torture_fail(torture, "setup copy chunk error");
+	}
+
+	cc_copy.chunks[0].source_off = 0;
+	cc_copy.chunks[0].target_off = 0;
+	cc_copy.chunks[0].length = 4096;
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_equal(torture, status,
+				      NT_STATUS_ACCESS_DENIED,
+				      "FSCTL_SRV_COPYCHUNK");
+
+	smb2_util_close(tree, src_h);
+	smb2_util_close(tree, dest_h);
+
+	/* no read permission on dest */
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   1, /* 1 chunk */
+				   &src_h, 4096, /* fill 4096 byte src file */
+				   SEC_RIGHTS_FILE_ALL,
+				   &dest_h, 0,	/* 0 byte dest file */
+				   (SEC_RIGHTS_FILE_WRITE
+				    | SEC_RIGHTS_FILE_EXECUTE),
+				   &cc_copy,
+				   &ioctl);
+	if (!ok) {
+		torture_fail(torture, "setup copy chunk error");
+	}
+
+	cc_copy.chunks[0].source_off = 0;
+	cc_copy.chunks[0].target_off = 0;
+	cc_copy.chunks[0].length = 4096;
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	/*
+	 * FSCTL_SRV_COPYCHUNK requires read permission on dest,
+	 * FSCTL_SRV_COPYCHUNK_WRITE (not supported by Samba) on the other hand
+	 * does not.
+	 */
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_equal(torture, status,
+				      NT_STATUS_ACCESS_DENIED,
+				      "FSCTL_SRV_COPYCHUNK");
+
+	smb2_util_close(tree, src_h);
+	smb2_util_close(tree, dest_h);
+	talloc_free(tmp_ctx);
+
+	return true;
+}
+
+static bool test_ioctl_copy_chunk_src_exceed(struct torture_context *torture,
+					     struct smb2_tree *tree)
+{
+	struct smb2_handle src_h;
+	struct smb2_handle dest_h;
+	NTSTATUS status;
+	union smb_ioctl ioctl;
+	TALLOC_CTX *tmp_ctx = talloc_new(tree);
+	struct srv_copychunk_copy cc_copy;
+	struct srv_copychunk_rsp cc_rsp;
+	enum ndr_err_code ndr_ret;
+	bool ok;
+
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   1, /* 1 chunk */
+				   &src_h, 4096, /* fill 4096 byte src file */
+				   SEC_RIGHTS_FILE_ALL,
+				   &dest_h, 0,	/* 0 byte dest file */
+				   SEC_RIGHTS_FILE_ALL,
+				   &cc_copy,
+				   &ioctl);
+	if (!ok) {
+		torture_fail(torture, "setup copy chunk error");
+	}
+
+	/* Request copy where off + length exceeds size of src */
+	cc_copy.chunks[0].source_off = 1024;
+	cc_copy.chunks[0].target_off = 0;
+	cc_copy.chunks[0].length = 4096;
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_equal(torture, status,
+				      NT_STATUS_INVALID_VIEW_SIZE,
+				      "FSCTL_SRV_COPYCHUNK oversize");
+
+	/* Request copy where length exceeds size of src */
+	cc_copy.chunks[0].source_off = 1024;
+	cc_copy.chunks[0].target_off = 0;
+	cc_copy.chunks[0].length = 3072;
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_ok(torture, status,
+				   "FSCTL_SRV_COPYCHUNK just right");
+
+	ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx,
+				       &cc_rsp,
+			(ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_pull_srv_copychunk_rsp");
+
+	ok = check_copy_chunk_rsp(torture, &cc_rsp,
+				  1,	/* chunks written */
+				  0,	/* chunk bytes unsuccessfully written */
+				  3072); /* total bytes written */
+	if (!ok) {
+		torture_fail(torture, "bad copy chunk response data");
+	}
+
+	ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 3072, 1024);
+	if (!ok) {
+		torture_fail(torture, "inconsistent file data");
+	}
+
+	smb2_util_close(tree, src_h);
+	smb2_util_close(tree, dest_h);
+	talloc_free(tmp_ctx);
+	return true;
+}
+
+static bool
+test_ioctl_copy_chunk_src_exceed_multi(struct torture_context *torture,
+				       struct smb2_tree *tree)
+{
+	struct smb2_handle src_h;
+	struct smb2_handle dest_h;
+	NTSTATUS status;
+	union smb_ioctl ioctl;
+	TALLOC_CTX *tmp_ctx = talloc_new(tree);
+	struct srv_copychunk_copy cc_copy;
+	struct srv_copychunk_rsp cc_rsp;
+	enum ndr_err_code ndr_ret;
+	bool ok;
+
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   2, /* 2 chunks */
+				   &src_h, 8192, /* fill 8192 byte src file */
+				   SEC_RIGHTS_FILE_ALL,
+				   &dest_h, 0,	/* 0 byte dest file */
+				   SEC_RIGHTS_FILE_ALL,
+				   &cc_copy,
+				   &ioctl);
+	if (!ok) {
+		torture_fail(torture, "setup copy chunk error");
+	}
+
+	/* Request copy where off + length exceeds size of src */
+	cc_copy.chunks[0].source_off = 0;
+	cc_copy.chunks[0].target_off = 0;
+	cc_copy.chunks[0].length = 4096;
+
+	cc_copy.chunks[1].source_off = 4096;
+	cc_copy.chunks[1].target_off = 4096;
+	cc_copy.chunks[1].length = 8192;
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_equal(torture, status,
+				      NT_STATUS_INVALID_VIEW_SIZE,
+				      "FSCTL_SRV_COPYCHUNK oversize");
+	ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx,
+				       &cc_rsp,
+			(ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp);
+	torture_assert_ndr_success(torture, ndr_ret, "unmarshalling response");
+
+	/* first chunk should still be written */
+	ok = check_copy_chunk_rsp(torture, &cc_rsp,
+				  1,	/* chunks written */
+				  0,	/* chunk bytes unsuccessfully written */
+				  4096); /* total bytes written */
+	if (!ok) {
+		torture_fail(torture, "bad copy chunk response data");
+	}
+	ok = check_pattern(torture, tree, tmp_ctx, dest_h, 0, 4096, 0);
+	if (!ok) {
+		torture_fail(torture, "inconsistent file data");
+	}
+
+	smb2_util_close(tree, src_h);
+	smb2_util_close(tree, dest_h);
+	talloc_free(tmp_ctx);
+	return true;
+}
+
+static bool test_ioctl_copy_chunk_sparse_dest(struct torture_context *torture,
+					      struct smb2_tree *tree)
+{
+	struct smb2_handle src_h;
+	struct smb2_handle dest_h;
+	NTSTATUS status;
+	union smb_ioctl ioctl;
+	struct smb2_read r;
+	TALLOC_CTX *tmp_ctx = talloc_new(tree);
+	struct srv_copychunk_copy cc_copy;
+	struct srv_copychunk_rsp cc_rsp;
+	enum ndr_err_code ndr_ret;
+	bool ok;
+	int i;
+
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   1, /* 1 chunk */
+				   &src_h, 4096, /* fill 4096 byte src file */
+				   SEC_RIGHTS_FILE_ALL,
+				   &dest_h, 0,	/* 0 byte dest file */
+				   SEC_RIGHTS_FILE_ALL,
+				   &cc_copy,
+				   &ioctl);
+	if (!ok) {
+		torture_fail(torture, "setup copy chunk error");
+	}
+
+	/* copy all src file data (via a single chunk desc) */
+	cc_copy.chunks[0].source_off = 0;
+	cc_copy.chunks[0].target_off = 4096;
+	cc_copy.chunks[0].length = 4096;
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_ok(torture, status, "FSCTL_SRV_COPYCHUNK");
+
+	ndr_ret = ndr_pull_struct_blob(&ioctl.smb2.out.out, tmp_ctx,
+				       &cc_rsp,
+			(ndr_pull_flags_fn_t)ndr_pull_srv_copychunk_rsp);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_pull_srv_copychunk_rsp");
+
+	ok = check_copy_chunk_rsp(torture, &cc_rsp,
+				  1,	/* chunks written */
+				  0,	/* chunk bytes unsuccessfully written */
+				  4096); /* total bytes written */
+	if (!ok) {
+		torture_fail(torture, "bad copy chunk response data");
+	}
+
+	/* check for zeros in first 4k */
+	ZERO_STRUCT(r);
+	r.in.file.handle = dest_h;
+	r.in.length      = 4096;
+	r.in.offset      = 0;
+	status = smb2_read(tree, tmp_ctx, &r);
+	torture_assert_ntstatus_ok(torture, status, "read");
+
+	torture_assert_u64_equal(torture, r.out.data.length, 4096,
+				 "read data len mismatch");
+
+	for (i = 0; i < 4096; i++) {
+		torture_assert(torture, (r.out.data.data[i] == 0),
+			       "sparse did not pass class");
+	}
+
+	ok = check_pattern(torture, tree, tmp_ctx, dest_h, 4096, 4096, 0);
+	if (!ok) {
+		torture_fail(torture, "inconsistent file data");
+	}
+
+	smb2_util_close(tree, src_h);
+	smb2_util_close(tree, dest_h);
+	talloc_free(tmp_ctx);
+	return true;
+}
+
+/*
+ * set the ioctl MaxOutputResponse size to less than
+ * sizeof(struct srv_copychunk_rsp)
+ */
+static bool test_ioctl_copy_chunk_max_output_sz(struct torture_context *torture,
+						struct smb2_tree *tree)
+{
+	struct smb2_handle src_h;
+	struct smb2_handle dest_h;
+	NTSTATUS status;
+	union smb_ioctl ioctl;
+	TALLOC_CTX *tmp_ctx = talloc_new(tree);
+	struct srv_copychunk_copy cc_copy;
+	enum ndr_err_code ndr_ret;
+	bool ok;
+
+	ok = test_setup_copy_chunk(torture, tree, tmp_ctx,
+				   1, /* 1 chunk */
+				   &src_h, 4096, /* fill 4096 byte src file */
+				   SEC_RIGHTS_FILE_ALL,
+				   &dest_h, 0,	/* 0 byte dest file */
+				   SEC_RIGHTS_FILE_ALL,
+				   &cc_copy,
+				   &ioctl);
+	if (!ok) {
+		torture_fail(torture, "setup copy chunk error");
+	}
+
+	cc_copy.chunks[0].source_off = 0;
+	cc_copy.chunks[0].target_off = 0;
+	cc_copy.chunks[0].length = 4096;
+	/* req is valid, but use undersize max_response_size */
+	ioctl.smb2.in.max_response_size = sizeof(struct srv_copychunk_rsp) - 1;
+
+	ndr_ret = ndr_push_struct_blob(&ioctl.smb2.in.out, tmp_ctx,
+				       &cc_copy,
+			(ndr_push_flags_fn_t)ndr_push_srv_copychunk_copy);
+	torture_assert_ndr_success(torture, ndr_ret,
+				   "ndr_push_srv_copychunk_copy");
+
+	status = smb2_ioctl(tree, tmp_ctx, &ioctl.smb2);
+	torture_assert_ntstatus_equal(torture, status,
+				      NT_STATUS_INVALID_PARAMETER,
+				      "FSCTL_SRV_COPYCHUNK");
+
+	smb2_util_close(tree, src_h);
+	smb2_util_close(tree, dest_h);
+	talloc_free(tmp_ctx);
+	return true;
+}
+
 /*
    basic testing of SMB2 ioctls
 */
@@ -875,6 +1570,22 @@ struct torture_suite *torture_smb2_ioctl_init(void)
 				     test_ioctl_copy_chunk_src_lck);
 	torture_suite_add_1smb2_test(suite, "copy_chunk_dest_lock",
 				     test_ioctl_copy_chunk_dest_lck);
+	torture_suite_add_1smb2_test(suite, "copy_chunk_bad_key",
+				     test_ioctl_copy_chunk_bad_key);
+	torture_suite_add_1smb2_test(suite, "copy_chunk_src_is_dest",
+				     test_ioctl_copy_chunk_src_is_dest);
+	torture_suite_add_1smb2_test(suite, "copy_chunk_src_is_dest_overlap",
+				     test_ioctl_copy_chunk_src_is_dest_overlap);
+	torture_suite_add_1smb2_test(suite, "copy_chunk_bad_access",
+				     test_ioctl_copy_chunk_bad_access);
+	torture_suite_add_1smb2_test(suite, "copy_chunk_src_exceed",
+				     test_ioctl_copy_chunk_src_exceed);
+	torture_suite_add_1smb2_test(suite, "copy_chunk_src_exceed_multi",
+				     test_ioctl_copy_chunk_src_exceed_multi);
+	torture_suite_add_1smb2_test(suite, "copy_chunk_sparse_dest",
+				     test_ioctl_copy_chunk_sparse_dest);
+	torture_suite_add_1smb2_test(suite, "copy_chunk_max_output_sz",
+				     test_ioctl_copy_chunk_max_output_sz);
 
 	suite->description = talloc_strdup(suite, "SMB2-IOCTL tests");
 
-- 
1.7.10.4



More information about the samba-technical mailing list