s4: torture: Add smb2.notify.handle-permissions test.

Add knownfail entry.

CVE-2020-14318

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

Signed-off-by: Jeremy Allison <jra@samba.org>

static bool torture_smb2_notify_close(struct torture_context *torture,
				struct smb2_tree *tree1)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	struct smb2_handle h1;
	struct smb2_request *req;

	smb2_deltree(tree1, BASEDIR_CNC);
	smb2_util_rmdir(tree1, BASEDIR_CNC);

	torture_comment(torture, "TESTING CHANGE NOTIFY FOLLOWED BY ULOGOFF\n");

	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_CNC;

	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN;
	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify,
	   on file or directory name changes */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.recursive = true;

	req = smb2_notify_send(tree1, &(notify.smb2));

	WAIT_FOR_ASYNC_RESPONSE(req);

	status = smb2_util_close(tree1, h1);
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, STATUS_NOTIFY_CLEANUP);
	CHECK_VAL(notify.smb2.out.num_changes, 0);

done:
	smb2_deltree(tree1, BASEDIR_CNC);
	return ret;
}

A flaw was found in the way samba handled file and directory permissions. An authenticated user could use this flaw to gain access to certain file and directory information which otherwise would be unavailable to the attacker.


struct torture_suite *torture_smb2_notify_inotify_init(TALLOC_CTX *ctx)
{
	struct torture_suite *suite = torture_suite_create(ctx, "notify-inotify");

	suite->description = talloc_strdup(suite, "SMB2-NOTIFY tests that use inotify");

	torture_suite_add_2smb2_test(suite, "inotify-rename", torture_smb2_inotify_rename);

	return suite;
}


static bool torture_smb2_notify_rmdir3(struct torture_context *torture,
				       struct smb2_tree *tree1,
				       struct smb2_tree *tree2)
{
	return torture_smb2_notify_rmdir(torture, tree1, tree2, false);
}


static bool torture_smb2_notify_mask(struct torture_context *torture,
				     struct smb2_tree *tree1,
				     struct smb2_tree *tree2)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io, io1;
	struct smb2_handle h1, h2;
	uint32_t mask;
	int i;
	char c = 1;
	union smb_setfileinfo sinfo;

	smb2_deltree(tree1, BASEDIR_MSK);
	smb2_util_rmdir(tree1, BASEDIR_MSK);

	torture_comment(torture, "TESTING CHANGE NOTIFY COMPLETION FILTERS\n");

	ZERO_STRUCT(h1);
	ZERO_STRUCT(h2);
	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_MSK;

	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.recursive = true;

#define NOTIFY_MASK_TEST(test_name, setup, op, cleanup, Action, \
			 expected, nchanges) \
	do { \
	do { for (mask=i=0;i<32;i++) { \
		struct smb2_request *req; \
		status = smb2_create(tree1, torture, &(io.smb2)); \
		CHECK_STATUS(status, NT_STATUS_OK); \
		h1 = io.smb2.out.file.handle; \
		setup \
		notify.smb2.in.file.handle = h1;	\
		notify.smb2.in.completion_filter = ((uint32_t)1<<i); \
		/* cancel initial requests so the buffer is setup */	\
		req = smb2_notify_send(tree1, &(notify.smb2)); \
		smb2_cancel(req); \
		status = smb2_notify_recv(req, torture, &(notify.smb2)); \
		CHECK_STATUS(status, NT_STATUS_CANCELLED); \
		/* send the change notify request */ \
		req = smb2_notify_send(tree1, &(notify.smb2)); \
		op \
		smb_msleep(200); smb2_cancel(req); \
		status = smb2_notify_recv(req, torture, &(notify.smb2)); \
		cleanup \
		smb2_util_close(tree1, h1); \
		if (NT_STATUS_EQUAL(status, NT_STATUS_CANCELLED)) continue; \
		CHECK_STATUS(status, NT_STATUS_OK); \
		/* special case to cope with file rename behaviour */ \
		if (nchanges == 2 && notify.smb2.out.num_changes == 1 && \
		    notify.smb2.out.changes[0].action == \
			NOTIFY_ACTION_MODIFIED && \
		    ((expected) & FILE_NOTIFY_CHANGE_ATTRIBUTES) && \
		    Action == NOTIFY_ACTION_OLD_NAME) { \
			torture_comment(torture, \
				"(rename file special handling OK)\n"); \
		} else if (nchanges != notify.smb2.out.num_changes) { \
			torture_result(torture, TORTURE_FAIL, \
			       "ERROR: nchanges=%d expected=%d "\
			       "action=%d filter=0x%08x\n", \
			       notify.smb2.out.num_changes, \
			       nchanges, \
			       notify.smb2.out.changes[0].action, \
			       notify.smb2.in.completion_filter); \
			ret = false; \
		} else if (notify.smb2.out.changes[0].action != Action) { \
			torture_result(torture, TORTURE_FAIL, \
			       "ERROR: nchanges=%d action=%d " \
			       "expectedAction=%d filter=0x%08x\n", \
			       notify.smb2.out.num_changes, \
			       notify.smb2.out.changes[0].action, \
			       Action, \
			       notify.smb2.in.completion_filter); \
			ret = false; \
		} else if (strcmp(notify.smb2.out.changes[0].name.s, \
			   "tname1") != 0) { \
			torture_result(torture, TORTURE_FAIL, \
			       "ERROR: nchanges=%d action=%d " \
			       "filter=0x%08x name=%s\n", \
			       notify.smb2.out.num_changes, \
			       notify.smb2.out.changes[0].action, \
			       notify.smb2.in.completion_filter, \
			       notify.smb2.out.changes[0].name.s);	\
			ret = false; \
		} \
		mask |= ((uint32_t)1<<i); \
	} \
	} while (0); \
	} while (0);

	torture_comment(torture, "Testing mkdir\n");
	NOTIFY_MASK_TEST("Testing mkdir",;,
			 smb2_util_mkdir(tree2, BASEDIR_MSK "\\tname1");,
			 smb2_util_rmdir(tree2, BASEDIR_MSK "\\tname1");,
			 NOTIFY_ACTION_ADDED,
			 FILE_NOTIFY_CHANGE_DIR_NAME, 1);

	torture_comment(torture, "Testing create file\n");
	ZERO_STRUCT(io1.smb2);
	io1.generic.level = RAW_OPEN_SMB2;
	io1.smb2.in.create_flags = 0;
	io1.smb2.in.desired_access = SEC_FILE_ALL;
	io1.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io1.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io1.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io1.smb2.in.security_flags = 0;
	io1.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE;
	io1.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io1.smb2.in.fname = BASEDIR_MSK "\\tname1";

	NOTIFY_MASK_TEST("Testing create file",;,
			 smb2_util_close(tree2, custom_smb2_create(tree2,
						torture, &(io1.smb2)));,
			 smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1");,
			 NOTIFY_ACTION_ADDED,
			 FILE_NOTIFY_CHANGE_FILE_NAME, 1);

	torture_comment(torture, "Testing unlink\n");
	NOTIFY_MASK_TEST("Testing unlink",
			 smb2_util_close(tree2, custom_smb2_create(tree2,
						torture, &(io1.smb2)));,
			 smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1");,
			 ;,
			 NOTIFY_ACTION_REMOVED,
			 FILE_NOTIFY_CHANGE_FILE_NAME, 1);

	torture_comment(torture, "Testing rmdir\n");
	NOTIFY_MASK_TEST("Testing rmdir",
			 smb2_util_mkdir(tree2, BASEDIR_MSK "\\tname1");,
			 smb2_util_rmdir(tree2, BASEDIR_MSK "\\tname1");,
			 ;,
			 NOTIFY_ACTION_REMOVED,
			 FILE_NOTIFY_CHANGE_DIR_NAME, 1);

	torture_comment(torture, "Testing rename file\n");
	ZERO_STRUCT(sinfo);
	sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION;
	sinfo.rename_information.in.file.handle = h1;
	sinfo.rename_information.in.overwrite = true;
	sinfo.rename_information.in.root_fid = 0;
	sinfo.rename_information.in.new_name = BASEDIR_MSK "\\tname2";
	NOTIFY_MASK_TEST("Testing rename file",
			 smb2_util_close(tree2, custom_smb2_create(tree2,
						torture, &(io1.smb2)));,
			 smb2_setinfo_file(tree2, &sinfo);,
			 smb2_util_unlink(tree2, BASEDIR_MSK "\\tname2");,
			 NOTIFY_ACTION_OLD_NAME,
			 FILE_NOTIFY_CHANGE_FILE_NAME, 2);

	torture_comment(torture, "Testing rename dir\n");
	ZERO_STRUCT(sinfo);
	sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION;
	sinfo.rename_information.in.file.handle = h1;
	sinfo.rename_information.in.overwrite = true;
	sinfo.rename_information.in.root_fid = 0;
	sinfo.rename_information.in.new_name = BASEDIR_MSK "\\tname2";
	NOTIFY_MASK_TEST("Testing rename dir",
		smb2_util_mkdir(tree2, BASEDIR_MSK "\\tname1");,
		smb2_setinfo_file(tree2, &sinfo);,
		smb2_util_rmdir(tree2, BASEDIR_MSK "\\tname2");,
		NOTIFY_ACTION_OLD_NAME,
		FILE_NOTIFY_CHANGE_DIR_NAME, 2);

	torture_comment(torture, "Testing set path attribute\n");
	NOTIFY_MASK_TEST("Testing set path attribute",
		smb2_util_close(tree2, custom_smb2_create(tree2,
				       torture, &(io.smb2)));,
		smb2_util_setatr(tree2, BASEDIR_MSK "\\tname1",
				 FILE_ATTRIBUTE_HIDDEN);,
		smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1");,
		NOTIFY_ACTION_MODIFIED,
		FILE_NOTIFY_CHANGE_ATTRIBUTES, 1);

	torture_comment(torture, "Testing set path write time\n");
	ZERO_STRUCT(sinfo);
	sinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION;
	sinfo.generic.in.file.handle = h1;
	sinfo.basic_info.in.write_time = 1000;
	NOTIFY_MASK_TEST("Testing set path write time",
		smb2_util_close(tree2, custom_smb2_create(tree2,
				       torture, &(io1.smb2)));,
		smb2_setinfo_file(tree2, &sinfo);,
		smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1");,
		NOTIFY_ACTION_MODIFIED,
		FILE_NOTIFY_CHANGE_LAST_WRITE, 1);

	if (torture_setting_bool(torture, "samba3", false)) {
		torture_comment(torture,
		       "Samba3 does not yet support create times "
		       "everywhere\n");
	}
	else {
		ZERO_STRUCT(sinfo);
	        sinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION;
		sinfo.generic.in.file.handle = h1;
	        sinfo.basic_info.in.create_time = 0;
		torture_comment(torture, "Testing set file create time\n");
		NOTIFY_MASK_TEST("Testing set file create time",
			smb2_create_complex_file(torture, tree2,
			BASEDIR_MSK "\\tname1", &h2);,
			smb2_setinfo_file(tree2, &sinfo);,
			(smb2_util_close(tree2, h2),
			 smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1"));,
			NOTIFY_ACTION_MODIFIED,
			FILE_NOTIFY_CHANGE_CREATION, 1);
	}

	ZERO_STRUCT(sinfo);
	sinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION;
	sinfo.generic.in.file.handle = h1;
	sinfo.basic_info.in.access_time = 0;
	torture_comment(torture, "Testing set file access time\n");
	NOTIFY_MASK_TEST("Testing set file access time",
		smb2_create_complex_file(torture,
			tree2,
			BASEDIR_MSK "\\tname1",
			&h2);,
		smb2_setinfo_file(tree2, &sinfo);,
		(smb2_util_close(tree2, h2),
		smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1"));,
		NOTIFY_ACTION_MODIFIED,
		FILE_NOTIFY_CHANGE_LAST_ACCESS, 1);

	ZERO_STRUCT(sinfo);
	sinfo.generic.level = RAW_SFILEINFO_BASIC_INFORMATION;
	sinfo.generic.in.file.handle = h1;
	sinfo.basic_info.in.change_time = 0;
	torture_comment(torture, "Testing set file change time\n");
	NOTIFY_MASK_TEST("Testing set file change time",
		smb2_create_complex_file(torture,
			tree2,
			BASEDIR_MSK "\\tname1",
			&h2);,
		smb2_setinfo_file(tree2, &sinfo);,
		(smb2_util_close(tree2, h2),
		smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1"));,
		NOTIFY_ACTION_MODIFIED,
		0, 1);

	torture_comment(torture, "Testing write\n");
	NOTIFY_MASK_TEST("Testing write",
		smb2_create_complex_file(torture,
			tree2,
			BASEDIR_MSK "\\tname1",
			&h2);,
		smb2_util_write(tree2, h2, &c, 10000, 1);,
		(smb2_util_close(tree2, h2),
		smb2_util_unlink(tree2, BASEDIR_MSK "\\tname1"));,
		NOTIFY_ACTION_MODIFIED,
		0, 1);

done:
	smb2_deltree(tree1, BASEDIR_MSK);
	return ret;
}


static bool torture_smb2_notify_handle_permissions(
		struct torture_context *torture,
		struct smb2_tree *tree)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	struct smb2_handle h1 = {{0}};
	struct smb2_request *req;

	smb2_deltree(tree, BASEDIR_HPERM);
	smb2_util_rmdir(tree, BASEDIR_HPERM);

	torture_comment(torture,
		"TESTING CHANGE NOTIFY "
		"ON A HANDLE WITHOUT PERMISSIONS\n");

	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_READ_ATTRIBUTE;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_HPERM;

	status = smb2_create(tree, torture, &io.smb2);
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify,
	   on file or directory name changes */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.recursive = true;

	req = smb2_notify_send(tree, &notify.smb2);
	torture_assert_goto(torture,
			req != NULL,
			ret,
			done,
			"smb2_notify_send failed\n");

	/*
	 * Cancel it, we don't really want to wait.
	 */
	smb2_cancel(req);
	status = smb2_notify_recv(req, torture, &notify.smb2);
	/* Handle h1 doesn't have permissions for ChangeNotify. */
	CHECK_STATUS(status, NT_STATUS_ACCESS_DENIED);

done:
	if (!smb2_util_handle_empty(h1)) {
		smb2_util_close(tree, h1);
	}
	smb2_deltree(tree, BASEDIR_HPERM);
	return ret;
}


static bool torture_smb2_notify_tree_disconnect(
		struct torture_context *torture,
		struct smb2_tree *tree)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	struct smb2_handle h1;
	struct smb2_request *req;

	smb2_deltree(tree, BASEDIR_TD);
	smb2_util_rmdir(tree, BASEDIR_TD);

	torture_comment(torture, "TESTING CHANGE NOTIFY+CANCEL FOLLOWED BY "
			"TREE-DISCONNECT\n");

	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_TD;

	status = smb2_create(tree, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify,
	   on file or directory name changes */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.recursive = true;

	req = smb2_notify_send(tree, &(notify.smb2));
	smb2_cancel(req);
	status = smb2_notify_recv(req, torture, &(notify.smb2));

	status = smb2_tdis(tree);
	CHECK_STATUS(status, NT_STATUS_OK);

	req = smb2_notify_send(tree, &(notify.smb2));

	smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, 0);

done:
	smb2_deltree(tree, BASEDIR_TD);
	return ret;
}


static bool torture_smb2_notify_tree(struct torture_context *torture,
			     struct smb2_tree *tree)
{
	bool ret = true;
	union smb_notify notify;
	union smb_open io;
	struct smb2_request *req;
	struct timeval tv;
	struct {
		const char *path;
		bool recursive;
		uint32_t filter;
		int expected;
		struct smb2_handle h1;
		int counted;
	} dirs[] = {
		{
			.path      = BASEDIR_TREE "\\abc",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 30,
		},
		{
			.path      = BASEDIR_TREE "\\zqy",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 8,
		},
		{
			.path      = BASEDIR_TREE "\\atsy",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 4,
		},
		{
			.path      = BASEDIR_TREE "\\abc\\foo",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 2,
		},
		{
			.path      = BASEDIR_TREE "\\abc\\blah",
			.recursive = true,
			.filter    =  FILE_NOTIFY_CHANGE_NAME,
			.expected  = 13,
		},
		{
			.path      = BASEDIR_TREE "\\abc\\blah",
			.recursive = false,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 7,
		},
		{
			.path      = BASEDIR_TREE "\\abc\\blah\\a",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 2,
		},
		{
			.path      = BASEDIR_TREE "\\abc\\blah\\b",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 2,
		},
		{
			.path      = BASEDIR_TREE "\\abc\\blah\\c",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 2,
		},
		{
			.path      = BASEDIR_TREE "\\abc\\fooblah",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 2,
		},
		{
			.path      = BASEDIR_TREE "\\zqy\\xx",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 2,
		},
		{
			.path      = BASEDIR_TREE "\\zqy\\yyy",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 2,
		},
		{
			.path      = BASEDIR_TREE "\\zqy\\..",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 40,
		},
		{
			.path      = BASEDIR_TREE,
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 40,
		},
		{
			.path      = BASEDIR_TREE,
			.recursive = false,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 6,
		},
		{
			.path      = BASEDIR_TREE "\\atsy",
			.recursive = false,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 4,
		},
		{
			.path      = BASEDIR_TREE "\\abc",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 24,
		},
		{
			.path      = BASEDIR_TREE "\\abc",
			.recursive = false,
			.filter    = FILE_NOTIFY_CHANGE_FILE_NAME,
			.expected  = 0,
		},
		{
			.path      = BASEDIR_TREE "\\abc",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_FILE_NAME,
			.expected  = 0,
		},
		{
			.path      = BASEDIR_TREE "\\abc",
			.recursive = true,
			.filter    = FILE_NOTIFY_CHANGE_NAME,
			.expected  = 24,
		},
	};
	int i;
	NTSTATUS status;
	bool all_done = false;

	smb2_deltree(tree, BASEDIR_TREE);
	smb2_util_rmdir(tree, BASEDIR_TREE);

	torture_comment(torture, "TESTING NOTIFY FOR DIFFERENT DEPTHS\n");

	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_TREE;
	status = smb2_create(tree, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 20000;

	/*
	  setup the directory tree, and the notify buffer on each directory
	*/
	for (i=0;i<ARRAY_SIZE(dirs);i++) {
		io.smb2.in.fname = dirs[i].path;
		status = smb2_create(tree, torture, &(io.smb2));
		CHECK_STATUS(status, NT_STATUS_OK);
		dirs[i].h1 = io.smb2.out.file.handle;

		notify.smb2.in.completion_filter = dirs[i].filter;
		notify.smb2.in.file.handle = dirs[i].h1;
		notify.smb2.in.recursive = dirs[i].recursive;
		req = smb2_notify_send(tree, &(notify.smb2));
		smb2_cancel(req);
		status = smb2_notify_recv(req, torture, &(notify.smb2));
		CHECK_STATUS(status, NT_STATUS_CANCELLED);
	}

	/* trigger 2 events in each dir */
	for (i=0;i<ARRAY_SIZE(dirs);i++) {
		char *path = talloc_asprintf(torture, "%s\\test.dir",
					     dirs[i].path);
		smb2_util_mkdir(tree, path);
		smb2_util_rmdir(tree, path);
		talloc_free(path);
	}

	/* give a bit of time for the events to propagate */
	tv = timeval_current();

	do {
		/* count events that have happened in each dir */
		for (i=0;i<ARRAY_SIZE(dirs);i++) {
			notify.smb2.in.completion_filter = dirs[i].filter;
			notify.smb2.in.file.handle = dirs[i].h1;
			notify.smb2.in.recursive = dirs[i].recursive;
			req = smb2_notify_send(tree, &(notify.smb2));
			smb2_cancel(req);
			notify.smb2.out.num_changes = 0;
			status = smb2_notify_recv(req, torture,
				 &(notify.smb2));
			dirs[i].counted += notify.smb2.out.num_changes;
		}

		all_done = true;

		for (i=0;i<ARRAY_SIZE(dirs);i++) {
			if (dirs[i].counted != dirs[i].expected) {
				all_done = false;
			}
		}
	} while (!all_done && timeval_elapsed(&tv) < 20);

	torture_comment(torture, "took %.4f seconds to propagate all events\n",
			timeval_elapsed(&tv));

	for (i=0;i<ARRAY_SIZE(dirs);i++) {
		if (dirs[i].counted != dirs[i].expected) {
			torture_comment(torture,
				"ERROR: i=%d expected %d got %d for '%s'\n",
				i, dirs[i].expected, dirs[i].counted,
				dirs[i].path);
			ret = false;
		}
	}

	/*
	  run from the back, closing and deleting
	*/
	for (i=ARRAY_SIZE(dirs)-1;i>=0;i--) {
		smb2_util_close(tree, dirs[i].h1);
		smb2_util_rmdir(tree, dirs[i].path);
	}

done:
	smb2_deltree(tree, BASEDIR_TREE);
	smb2_util_rmdir(tree, BASEDIR_TREE);
	return ret;
}


static bool torture_smb2_notify_rmdir(struct torture_context *torture,
				      struct smb2_tree *tree1,
				      struct smb2_tree *tree2,
				      bool initial_delete_on_close)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify = {};
	union smb_setfileinfo sfinfo = {};
	union smb_open io = {};
	struct smb2_handle h = {};
	struct smb2_request *req;

	torture_comment(torture, "TESTING NOTIFY CANCEL FOR DELETED DIR\n");

	smb2_deltree(tree1, BASEDIR_RMD);
	smb2_util_rmdir(tree1, BASEDIR_RMD);

	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access =
		NTCREATEX_SHARE_ACCESS_READ |
		NTCREATEX_SHARE_ACCESS_WRITE |
		NTCREATEX_SHARE_ACCESS_DELETE ;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_RMD;

	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h = io.smb2.out.file.handle;

	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h;
	notify.smb2.in.recursive = false;

	io.smb2.in.desired_access |= SEC_STD_DELETE;
	io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN;
	req = smb2_notify_send(tree1, &(notify.smb2));

	if (initial_delete_on_close) {
		status = smb2_util_rmdir(tree2, BASEDIR_RMD);
		CHECK_STATUS(status, NT_STATUS_OK);
	} else {
		status = smb2_create(tree2, torture, &(io.smb2));
		CHECK_STATUS(status, NT_STATUS_OK);

		sfinfo.generic.level = RAW_SFILEINFO_DISPOSITION_INFORMATION;
		sfinfo.generic.in.file.handle = io.smb2.out.file.handle;
		sfinfo.disposition_info.in.delete_on_close = 1;
		status = smb2_setinfo_file(tree2, &sfinfo);
		CHECK_STATUS(status, NT_STATUS_OK);

		smb2_util_close(tree2, io.smb2.out.file.handle);
	}

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_DELETE_PENDING);

done:

	smb2_util_close(tree1, h);
	smb2_deltree(tree1, BASEDIR_RMD);

	return ret;
}


static bool torture_smb2_notify_file(struct torture_context *torture,
				struct smb2_tree *tree)
{
	NTSTATUS status;
	bool ret = true;
	union smb_open io;
	union smb_close cl;
	union smb_notify notify;
	struct smb2_request *req;
	struct smb2_handle h1;
	const char *fname = BASEDIR_FL "\\file.txt";

	smb2_deltree(tree, BASEDIR_FL);
	smb2_util_rmdir(tree, BASEDIR_FL);

	torture_comment(torture, "TESTING CHANGE NOTIFY ON FILES\n");
	status = torture_smb2_testdir(tree, BASEDIR_FL, &h1);
	CHECK_STATUS(status, NT_STATUS_OK);

	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FLAG_MAXIMUM_ALLOWED;
	io.smb2.in.create_options = 0;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = fname;
	status = smb2_create(tree, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify,
	   on file or directory name changes */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_STREAM_NAME;
	notify.smb2.in.recursive = false;

	torture_comment(torture,
	"Testing if notifies on file handles are invalid (should be)\n");

	req = smb2_notify_send(tree, &(notify.smb2));
	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER);

	ZERO_STRUCT(cl.smb2);
	cl.close.level = RAW_CLOSE_SMB2;
	cl.close.in.file.handle = h1;
	status = smb2_close(tree, &(cl.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_util_unlink(tree, fname);
	CHECK_STATUS(status, NT_STATUS_OK);

done:
	smb2_deltree(tree, BASEDIR_FL);
	return ret;
}


static bool torture_smb2_notify_rmdir1(struct torture_context *torture,
				       struct smb2_tree *tree)
{
	return torture_smb2_notify_rmdir(torture, tree, tree, false);
}


static bool torture_smb2_notify_rmdir2(struct torture_context *torture,
				       struct smb2_tree *tree)
{
	return torture_smb2_notify_rmdir(torture, tree, tree, true);
}


struct torture_suite *torture_smb2_notify_init(TALLOC_CTX *ctx)
{
	struct torture_suite *suite = torture_suite_create(ctx, "notify");

	torture_suite_add_1smb2_test(suite, "valid-req", test_valid_request);
	torture_suite_add_1smb2_test(suite, "tcon", torture_smb2_notify_tcon);
	torture_suite_add_2smb2_test(suite, "dir", torture_smb2_notify_dir);
	torture_suite_add_2smb2_test(suite, "mask", torture_smb2_notify_mask);
	torture_suite_add_1smb2_test(suite, "tdis", torture_smb2_notify_tree_disconnect);
	torture_suite_add_1smb2_test(suite, "tdis1", torture_smb2_notify_tree_disconnect_1);
	torture_suite_add_2smb2_test(suite, "mask-change", torture_smb2_notify_mask_change);
	torture_suite_add_1smb2_test(suite, "close", torture_smb2_notify_close);
	torture_suite_add_1smb2_test(suite, "logoff", torture_smb2_notify_ulogoff);
	torture_suite_add_1smb2_test(suite, "session-reconnect", torture_smb2_notify_session_reconnect);
	torture_suite_add_2smb2_test(suite, "invalid-reauth", torture_smb2_notify_invalid_reauth);
	torture_suite_add_1smb2_test(suite, "tree", torture_smb2_notify_tree);
	torture_suite_add_2smb2_test(suite, "basedir", torture_smb2_notify_basedir);
	torture_suite_add_2smb2_test(suite, "double", torture_smb2_notify_double);
	torture_suite_add_1smb2_test(suite, "file", torture_smb2_notify_file);
	torture_suite_add_1smb2_test(suite, "tcp", torture_smb2_notify_tcp_disconnect);
	torture_suite_add_2smb2_test(suite, "rec", torture_smb2_notify_recursive);
	torture_suite_add_1smb2_test(suite, "overflow", torture_smb2_notify_overflow);
	torture_suite_add_1smb2_test(suite, "rmdir1",
				     torture_smb2_notify_rmdir1);
	torture_suite_add_1smb2_test(suite, "rmdir2",
				     torture_smb2_notify_rmdir2);
	torture_suite_add_2smb2_test(suite, "rmdir3",
				     torture_smb2_notify_rmdir3);
	torture_suite_add_2smb2_test(suite, "rmdir4",
				     torture_smb2_notify_rmdir4);
	torture_suite_add_1smb2_test(suite,
				    "handle-permissions",
				    torture_smb2_notify_handle_permissions);

	suite->description = talloc_strdup(suite, "SMB2-NOTIFY tests");

	return suite;
}


static bool torture_smb2_notify_basedir(struct torture_context *torture,
				struct smb2_tree *tree1,
				struct smb2_tree *tree2)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	struct smb2_handle h1;
	struct smb2_request *req1;

	smb2_deltree(tree1, BASEDIR_BAS);
	smb2_util_rmdir(tree1, BASEDIR_BAS);

	torture_comment(torture, "TESTING CHANGE NOTIFY BASEDIR EVENTS\n");

	/* get a handle on the directory */
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
	    NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
	io.smb2.in.impersonation_level = NTCREATEX_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_BAS;

	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* create a test file that will also be modified */
	io.smb2.in.fname = BASEDIR_BAS "\\tname1";
	io.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE;
	status =  smb2_create(tree2, torture, &(io.smb2));
	CHECK_STATUS(status,NT_STATUS_OK);
	smb2_util_close(tree2, io.smb2.out.file.handle);

	/* ask for a change notify, on attribute changes. */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_ATTRIBUTES;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.recursive = true;

	req1 = smb2_notify_send(tree1, &(notify.smb2));

	/* set attribute on the base dir */
	smb2_util_setatr(tree2, BASEDIR_BAS, FILE_ATTRIBUTE_HIDDEN);

	/* set attribute on a file to assure we receive a notification */
	smb2_util_setatr(tree2, BASEDIR_BAS "\\tname1", FILE_ATTRIBUTE_HIDDEN);
	smb_msleep(200);

	/* check how many responses were given, expect only 1 for the file */
	status = smb2_notify_recv(req1, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_MODIFIED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "tname1");

done:
	smb2_deltree(tree1, BASEDIR_BAS);
	return ret;
}


static bool torture_smb2_notify_session_reconnect(struct torture_context *torture,
				struct smb2_tree *tree1)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	struct smb2_handle h1;
	struct smb2_request *req;
	uint64_t previous_session_id = 0;
	struct smb2_session *session2 = NULL;

	smb2_deltree(tree1, BASEDIR_NSR);
	smb2_util_rmdir(tree1, BASEDIR_NSR);

	torture_comment(torture, "TESTING CHANGE NOTIFY FOLLOWED BY SESSION RECONNECT\n");

	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_NSR;

	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN;
	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify,
	   on file or directory name changes */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.recursive = true;

	req = smb2_notify_send(tree1, &(notify.smb2));

	WAIT_FOR_ASYNC_RESPONSE(req);

	previous_session_id = smb2cli_session_current_id(tree1->session->smbXcli);
	torture_assert(torture, torture_smb2_session_setup(torture,
		       tree1->session->transport,
		       previous_session_id,
		       torture, &session2),
		       "session setup with previous_session_id failed");

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, STATUS_NOTIFY_CLEANUP);
	CHECK_VAL(notify.smb2.out.num_changes, 0);

	status = smb2_logoff(tree1->session);
	CHECK_STATUS(status, NT_STATUS_USER_SESSION_DELETED);

	status = smb2_logoff(session2);
	CHECK_STATUS(status, NT_STATUS_OK);
done:
	smb2_deltree(tree1, BASEDIR_NSR);
	return ret;
}


static bool torture_smb2_notify_dir(struct torture_context *torture,
			      struct smb2_tree *tree1,
			      struct smb2_tree *tree2)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	union smb_close cl;
	int i, count;
	struct smb2_handle h1 = {{0}};
	struct smb2_handle h2 = {{0}};
	struct smb2_request *req, *req2;
	const char *fname = BASEDIR_DIR "\\subdir-name";
	extern int torture_numops;

	torture_comment(torture, "TESTING CHANGE NOTIFY ON DIRECTORIES\n");

	smb2_deltree(tree1, BASEDIR_DIR);
	smb2_util_rmdir(tree1, BASEDIR_DIR);
	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_DIR;

	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN;
	io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ;
	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h2 = io.smb2.out.file.handle;

	/* ask for a change notify,
	   on file or directory name changes */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.recursive = true;

	torture_comment(torture, "Testing notify cancel\n");

	req = smb2_notify_send(tree1, &(notify.smb2));
	smb2_cancel(req);
	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_CANCELLED);

	torture_comment(torture, "Testing notify mkdir\n");

	req = smb2_notify_send(tree1, &(notify.smb2));
	smb2_util_mkdir(tree2, fname);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_ADDED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name");

	torture_comment(torture, "Testing notify rmdir\n");

	req = smb2_notify_send(tree1, &(notify.smb2));
	smb2_util_rmdir(tree2, fname);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_REMOVED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name");

	torture_comment(torture,
		"Testing notify mkdir - rmdir - mkdir - rmdir\n");

	smb2_util_mkdir(tree2, fname);
	smb2_util_rmdir(tree2, fname);
	smb2_util_mkdir(tree2, fname);
	smb2_util_rmdir(tree2, fname);
	smb_msleep(200);
	req = smb2_notify_send(tree1, &(notify.smb2));
	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, 4);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_ADDED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name");
	CHECK_VAL(notify.smb2.out.changes[1].action, NOTIFY_ACTION_REMOVED);
	CHECK_WIRE_STR(notify.smb2.out.changes[1].name, "subdir-name");
	CHECK_VAL(notify.smb2.out.changes[2].action, NOTIFY_ACTION_ADDED);
	CHECK_WIRE_STR(notify.smb2.out.changes[2].name, "subdir-name");
	CHECK_VAL(notify.smb2.out.changes[3].action, NOTIFY_ACTION_REMOVED);
	CHECK_WIRE_STR(notify.smb2.out.changes[3].name, "subdir-name");

	count = torture_numops;
	torture_comment(torture,
		"Testing buffered notify on create of %d files\n", count);
	for (i=0;i<count;i++) {
		struct smb2_handle h12;
		char *fname2 = talloc_asprintf(torture,
						BASEDIR_DIR "\\test%d.txt",
						i);

		ZERO_STRUCT(io.smb2);
		io.generic.level = RAW_OPEN_SMB2;
	        io.smb2.in.create_flags = 0;
		io.smb2.in.desired_access = SEC_FILE_ALL;
	        io.smb2.in.create_options =
		    NTCREATEX_OPTIONS_NON_DIRECTORY_FILE;
		io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	        io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
					NTCREATEX_SHARE_ACCESS_WRITE;
		io.smb2.in.alloc_size = 0;
	        io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
		io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	        io.smb2.in.security_flags = 0;
		io.smb2.in.fname = fname2;

		status = smb2_create(tree1, torture, &(io.smb2));
		if (!NT_STATUS_EQUAL(status, NT_STATUS_OK)) {
			torture_comment(torture, "Failed to create %s \n",
			       fname);
			ret = false;
			goto done;
		}
		h12 = io.smb2.out.file.handle;
		talloc_free(fname2);
		smb2_util_close(tree1, h12);
	}

	/* (1st notify) setup a new notify on a different directory handle.
	   This new notify won't see the events above. */
	notify.smb2.in.file.handle = h2;
	req2 = smb2_notify_send(tree1, &(notify.smb2));

	/* (2nd notify) whereas this notify will see the above buffered events,
	   and it directly returns the buffered events */
	notify.smb2.in.file.handle = h1;
	req = smb2_notify_send(tree1, &(notify.smb2));

	status = smb2_util_unlink(tree1, BASEDIR_DIR "\\nonexistent.txt");
	CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);

	/* (1st unlink) as the 2nd notify directly returns,
	   this unlink is only seen by the 1st notify and
	   the 3rd notify (later) */
	torture_comment(torture,
		"Testing notify on unlink for the first file\n");
	status = smb2_util_unlink(tree2, BASEDIR_DIR "\\test0.txt");
	CHECK_STATUS(status, NT_STATUS_OK);

	/* receive the reply from the 2nd notify */
	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	CHECK_VAL(notify.smb2.out.num_changes, count);
	for (i=1;i<count;i++) {
		CHECK_VAL(notify.smb2.out.changes[i].action,
			  NOTIFY_ACTION_ADDED);
	}
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "test0.txt");

	torture_comment(torture, "and now from the 1st notify\n");
	status = smb2_notify_recv(req2, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_REMOVED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "test0.txt");

	torture_comment(torture,
		"(3rd notify) this notify will only see the 1st unlink\n");
	req = smb2_notify_send(tree1, &(notify.smb2));

	status = smb2_util_unlink(tree1, BASEDIR_DIR "\\nonexistent.txt");
	CHECK_STATUS(status, NT_STATUS_OBJECT_NAME_NOT_FOUND);

	for (i=1;i<count;i++) {
		char *fname2 = talloc_asprintf(torture,
			      BASEDIR_DIR "\\test%d.txt", i);
		status = smb2_util_unlink(tree2, fname2);
		CHECK_STATUS(status, NT_STATUS_OK);
		talloc_free(fname2);
	}

	/* receive the 3rd notify */
	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_REMOVED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "test0.txt");

	/* and we now see the rest of the unlink calls on both
	 * directory handles */
	notify.smb2.in.file.handle = h1;
	sleep(3);
	req = smb2_notify_send(tree1, &(notify.smb2));
	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, count-1);
	for (i=0;i<notify.smb2.out.num_changes;i++) {
		CHECK_VAL(notify.smb2.out.changes[i].action,
			  NOTIFY_ACTION_REMOVED);
	}
	notify.smb2.in.file.handle = h2;
	req = smb2_notify_send(tree1, &(notify.smb2));
	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, count-1);
	for (i=0;i<notify.smb2.out.num_changes;i++) {
		CHECK_VAL(notify.smb2.out.changes[i].action,
			  NOTIFY_ACTION_REMOVED);
	}

	torture_comment(torture,
	"Testing if a close() on the dir handle triggers the notify reply\n");

	notify.smb2.in.file.handle = h1;
	req = smb2_notify_send(tree1, &(notify.smb2));

	ZERO_STRUCT(cl.smb2);
	cl.smb2.level = RAW_CLOSE_SMB2;
	cl.smb2.in.file.handle = h1;
	status = smb2_close(tree1, &(cl.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, STATUS_NOTIFY_CLEANUP);
	CHECK_VAL(notify.smb2.out.num_changes, 9);

done:
	smb2_util_close(tree1, h1);
	smb2_util_close(tree1, h2);
	smb2_deltree(tree1, BASEDIR_DIR);
	return ret;
}


static struct smb2_handle custom_smb2_create(struct smb2_tree *tree,
						struct torture_context *torture,
						struct smb2_create *smb2)
{
	struct smb2_handle h1;
	bool ret = true;
	NTSTATUS status;
	smb2_deltree(tree, smb2->in.fname);
	status = smb2_create(tree, torture, smb2);
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = smb2->out.file.handle;
done:
	if (!ret) {
		h1 = (struct smb2_handle) {
			.data = { 0 , 0},
		};
	}
	return h1;
}


static bool torture_smb2_notify_tcp_disconnect(
		struct torture_context *torture,
		struct smb2_tree *tree)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	struct smb2_handle h1;
	struct smb2_request *req;

	smb2_deltree(tree, BASEDIR_NTCPD);
	smb2_util_rmdir(tree, BASEDIR_NTCPD);

	torture_comment(torture,
		"TESTING CHANGE NOTIFY FOLLOWED BY TCP DISCONNECT\n");

	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_NTCPD;

	status = smb2_create(tree, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify,
	   on file or directory name changes */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.recursive = true;

	req = smb2_notify_send(tree, &(notify.smb2));
	smb2_cancel(req);
	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_CANCELLED);

	notify.smb2.in.recursive = true;
	req = smb2_notify_send(tree, &(notify.smb2));
	smb2_transport_idle_handler(tree->session->transport,
				tcp_dis_handler, 250000, tree);
	tree = NULL;
	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_LOCAL_DISCONNECT);

done:
	return ret;
}


static bool torture_smb2_notify_invalid_reauth(struct torture_context *torture,
					       struct smb2_tree *tree1,
					       struct smb2_tree *tree2)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	struct smb2_handle h1;
	struct smb2_request *req;
	struct cli_credentials *invalid_creds;

	smb2_deltree(tree2, BASEDIR_IR);
	smb2_util_rmdir(tree2, BASEDIR_IR);

	torture_comment(torture, "TESTING CHANGE NOTIFY FOLLOWED BY invalid REAUTH\n");

	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_IR;

	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN;
	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify,
	   on file or directory name changes */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.recursive = true;

	req = smb2_notify_send(tree1, &(notify.smb2));

	WAIT_FOR_ASYNC_RESPONSE(req);

	invalid_creds = cli_credentials_init(torture);
	torture_assert(torture, (invalid_creds != NULL), "talloc error");
	cli_credentials_set_username(invalid_creds, "__none__invalid__none__", CRED_SPECIFIED);
	cli_credentials_set_domain(invalid_creds, "__none__invalid__none__", CRED_SPECIFIED);
	cli_credentials_set_password(invalid_creds, "__none__invalid__none__", CRED_SPECIFIED);
	cli_credentials_set_realm(invalid_creds, NULL, CRED_SPECIFIED);
	cli_credentials_set_workstation(invalid_creds, "", CRED_UNINITIALISED);

	status = smb2_session_setup_spnego(tree1->session,
					   invalid_creds,
					   0 /* previous_session_id */);
	CHECK_STATUS(status, NT_STATUS_LOGON_FAILURE);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, STATUS_NOTIFY_CLEANUP);
	CHECK_VAL(notify.smb2.out.num_changes, 0);

done:
	smb2_deltree(tree2, BASEDIR_IR);
	return ret;
}


static void notify_timeout(struct tevent_context *ev,
			   struct tevent_timer *te,
			   struct timeval current_time,
			   void *private_data)
{
	struct smb2_request *req = talloc_get_type_abort(
		private_data, struct smb2_request);

	smb2_cancel(req);
}


static bool torture_smb2_notify_ulogoff(struct torture_context *torture,
				struct smb2_tree *tree1)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	struct smb2_handle h1;
	struct smb2_request *req;

	smb2_deltree(tree1, BASEDIR_NUL);
	smb2_util_rmdir(tree1, BASEDIR_NUL);

	torture_comment(torture, "TESTING CHANGE NOTIFY FOLLOWED BY ULOGOFF\n");

	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_NUL;

	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN;
	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify,
	   on file or directory name changes */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.recursive = true;

	req = smb2_notify_send(tree1, &(notify.smb2));

	WAIT_FOR_ASYNC_RESPONSE(req);

	status = smb2_logoff(tree1->session);
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, STATUS_NOTIFY_CLEANUP);
	CHECK_VAL(notify.smb2.out.num_changes, 0);

done:
	smb2_deltree(tree1, BASEDIR_NUL);
	return ret;
}


static bool torture_smb2_notify_overflow(struct torture_context *torture,
				struct smb2_tree *tree)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	struct smb2_handle h1, h2;
	int count = 100;
	struct smb2_request *req1;
	int i;

	smb2_deltree(tree, BASEDIR_OVF);
	smb2_util_rmdir(tree, BASEDIR_OVF);

	torture_comment(torture, "TESTING CHANGE NOTIFY EVENT OVERFLOW\n");

	/* get a handle on the directory */
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
			    NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_OVF;

	status = smb2_create(tree, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify, on name changes. */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_NTTRANS;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h1;

	notify.smb2.in.recursive = true;
	req1 = smb2_notify_send(tree, &(notify.smb2));

	/* cancel initial requests so the buffer is setup */
	smb2_cancel(req1);
	status = smb2_notify_recv(req1, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_CANCELLED);

	/* open a lot of files, filling up the server side notify buffer */
	torture_comment(torture,
		"Testing overflowed buffer notify on create of %d files\n",
		count);

	for (i=0;i<count;i++) {
		char *fname = talloc_asprintf(torture,
			      BASEDIR_OVF "\\test%d.txt", i);
		union smb_open io1;
		ZERO_STRUCT(io1.smb2);
	        io1.generic.level = RAW_OPEN_SMB2;
		io1.smb2.in.create_flags = 0;
	        io1.smb2.in.desired_access = SEC_FILE_ALL;
		io1.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
		io1.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	        io1.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				    NTCREATEX_SHARE_ACCESS_WRITE;
	        io1.smb2.in.alloc_size = 0;
	        io1.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	        io1.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	        io1.smb2.in.security_flags = 0;
		io1.smb2.in.fname = fname;

		h2 = custom_smb2_create(tree, torture, &(io1.smb2));
		talloc_free(fname);
		smb2_util_close(tree, h2);
	}

	req1 = smb2_notify_send(tree, &(notify.smb2));
	status = smb2_notify_recv(req1, torture, &(notify.smb2));
	CHECK_STATUS(status, STATUS_NOTIFY_ENUM_DIR);
	CHECK_VAL(notify.smb2.out.num_changes, 0);

done:
	smb2_deltree(tree, BASEDIR_OVF);
	return ret;
}


static bool torture_smb2_notify_recursive(struct torture_context *torture,
				struct smb2_tree *tree1,
				struct smb2_tree *tree2)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io, io1;
	union smb_setfileinfo sinfo;
	struct smb2_handle h1;
	struct smb2_request *req1, *req2;

	smb2_deltree(tree1, BASEDIR_REC);
	smb2_util_rmdir(tree1, BASEDIR_REC);

	torture_comment(torture, "TESTING CHANGE NOTIFY WITH RECURSION\n");

	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_REC;

	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify, on file or directory name
	   changes. Setup both with and without recursion */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME |
				FILE_NOTIFY_CHANGE_ATTRIBUTES |
				FILE_NOTIFY_CHANGE_CREATION;
	notify.smb2.in.file.handle = h1;

	notify.smb2.in.recursive = true;
	req1 = smb2_notify_send(tree1, &(notify.smb2));
	smb2_cancel(req1);
	status = smb2_notify_recv(req1, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_CANCELLED);

	notify.smb2.in.recursive = false;
	req2 = smb2_notify_send(tree1, &(notify.smb2));
	smb2_cancel(req2);
	status = smb2_notify_recv(req2, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_CANCELLED);

	ZERO_STRUCT(io1.smb2);
	io1.generic.level = RAW_OPEN_SMB2;
	io1.smb2.in.create_flags = NTCREATEX_FLAGS_EXTENDED;
	io1.smb2.in.desired_access = SEC_RIGHTS_FILE_READ |
				SEC_RIGHTS_FILE_WRITE|
				SEC_RIGHTS_FILE_ALL;
	io1.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io1.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io1.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE |
				NTCREATEX_SHARE_ACCESS_DELETE;
	io1.smb2.in.alloc_size = 0;
	io1.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
	io1.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io1.smb2.in.security_flags = 0;
	io1.smb2.in.fname = BASEDIR_REC "\\subdir-name";
	status = smb2_create(tree2, torture, &(io1.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	smb2_util_close(tree2, io1.smb2.out.file.handle);

	io1.smb2.in.fname = BASEDIR_REC "\\subdir-name\\subname1";
	status = smb2_create(tree2, torture, &(io1.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	ZERO_STRUCT(sinfo);
	sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION;
	sinfo.rename_information.in.file.handle = io1.smb2.out.file.handle;
	sinfo.rename_information.in.overwrite = 0;
	sinfo.rename_information.in.root_fid = 0;
	sinfo.rename_information.in.new_name =
				BASEDIR_REC "\\subdir-name\\subname1-r";
	status = smb2_setinfo_file(tree2, &sinfo);
	CHECK_STATUS(status, NT_STATUS_OK);

	io1.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE;
	io1.smb2.in.fname = BASEDIR_REC "\\subdir-name\\subname2";
	status = smb2_create(tree2, torture, &(io1.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	ZERO_STRUCT(sinfo);
	sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION;
	sinfo.rename_information.in.file.handle = io1.smb2.out.file.handle;
	sinfo.rename_information.in.overwrite = true;
	sinfo.rename_information.in.root_fid = 0;
	sinfo.rename_information.in.new_name = BASEDIR_REC "\\subname2-r";
	status = smb2_setinfo_file(tree2, &sinfo);
	CHECK_STATUS(status, NT_STATUS_OK);

	io1.smb2.in.fname = BASEDIR_REC "\\subname2-r";
	io1.smb2.in.create_disposition = NTCREATEX_DISP_OPEN;
	status = smb2_create(tree2, torture, &(io1.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	ZERO_STRUCT(sinfo);
	sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION;
	sinfo.rename_information.in.file.handle = io1.smb2.out.file.handle;
	sinfo.rename_information.in.overwrite = true;
	sinfo.rename_information.in.root_fid = 0;
	sinfo.rename_information.in.new_name = BASEDIR_REC "\\subname3-r";
	status = smb2_setinfo_file(tree2, &sinfo);
	CHECK_STATUS(status, NT_STATUS_OK);

	notify.smb2.in.completion_filter = 0;
	notify.smb2.in.recursive = true;
	smb_msleep(200);
	req1 = smb2_notify_send(tree1, &(notify.smb2));

	status = smb2_util_rmdir(tree2,
		BASEDIR_REC "\\subdir-name\\subname1-r");
	CHECK_STATUS(status, NT_STATUS_OK);
	status = smb2_util_rmdir(tree2,
		BASEDIR_REC "\\subdir-name");
	CHECK_STATUS(status, NT_STATUS_OK);
	status = smb2_util_unlink(tree2, BASEDIR_REC "\\subname3-r");
	CHECK_STATUS(status, NT_STATUS_OK);

	notify.smb2.in.recursive = false;
	req2 = smb2_notify_send(tree1, &(notify.smb2));

	status = smb2_notify_recv(req1, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	CHECK_VAL(notify.smb2.out.num_changes, 9);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_ADDED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name");
	CHECK_VAL(notify.smb2.out.changes[1].action, NOTIFY_ACTION_ADDED);
	CHECK_WIRE_STR(notify.smb2.out.changes[1].name, "subdir-name\\subname1");
	CHECK_VAL(notify.smb2.out.changes[2].action, NOTIFY_ACTION_OLD_NAME);
	CHECK_WIRE_STR(notify.smb2.out.changes[2].name, "subdir-name\\subname1");
	CHECK_VAL(notify.smb2.out.changes[3].action, NOTIFY_ACTION_NEW_NAME);
	CHECK_WIRE_STR(notify.smb2.out.changes[3].name, "subdir-name\\subname1-r");
	CHECK_VAL(notify.smb2.out.changes[4].action, NOTIFY_ACTION_ADDED);
	CHECK_WIRE_STR(notify.smb2.out.changes[4].name, "subdir-name\\subname2");
	CHECK_VAL(notify.smb2.out.changes[5].action, NOTIFY_ACTION_REMOVED);
	CHECK_WIRE_STR(notify.smb2.out.changes[5].name, "subdir-name\\subname2");
	CHECK_VAL(notify.smb2.out.changes[6].action, NOTIFY_ACTION_ADDED);
	CHECK_WIRE_STR(notify.smb2.out.changes[6].name, "subname2-r");
	CHECK_VAL(notify.smb2.out.changes[7].action, NOTIFY_ACTION_OLD_NAME);
	CHECK_WIRE_STR(notify.smb2.out.changes[7].name, "subname2-r");
	CHECK_VAL(notify.smb2.out.changes[8].action, NOTIFY_ACTION_NEW_NAME);
	CHECK_WIRE_STR(notify.smb2.out.changes[8].name, "subname3-r");

done:
	smb2_deltree(tree1, BASEDIR_REC);
	return ret;
}


static bool torture_smb2_notify_double(struct torture_context *torture,
			struct smb2_tree *tree1,
			struct smb2_tree *tree2)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	struct smb2_handle h1;
	struct smb2_request *req1, *req2;

	smb2_deltree(tree1, BASEDIR_NDOH);
	smb2_util_rmdir(tree1, BASEDIR_NDOH);

	torture_comment(torture,
		"TESTING CHANGE NOTIFY TWICE ON ONE DIRECTORY\n");

	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_RIGHTS_FILE_READ|
				SEC_RIGHTS_FILE_WRITE|
				SEC_RIGHTS_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_NDOH;

	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify,
	   on file or directory name changes */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.recursive = true;

	req1 = smb2_notify_send(tree1, &(notify.smb2));
	smb2_cancel(req1);
	status = smb2_notify_recv(req1, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_CANCELLED);

	req2 = smb2_notify_send(tree1, &(notify.smb2));
	smb2_cancel(req2);
	status = smb2_notify_recv(req2, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_CANCELLED);

	smb2_util_mkdir(tree2, BASEDIR_NDOH "\\subdir-name");
	req1 = smb2_notify_send(tree1, &(notify.smb2));
	req2 = smb2_notify_send(tree1, &(notify.smb2));

	status = smb2_notify_recv(req1, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name");

	smb2_util_mkdir(tree2, BASEDIR_NDOH "\\subdir-name2");

	status = smb2_notify_recv(req2, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name2");

done:
	smb2_deltree(tree1, BASEDIR_NDOH);
	return ret;
}


static void tcp_dis_handler(struct smb2_transport *t, void *p)
{
	struct smb2_tree *tree = (struct smb2_tree *)p;
	smb2_transport_dead(tree->session->transport,
			NT_STATUS_LOCAL_DISCONNECT);
	t = NULL;
	tree = NULL;
}


static bool torture_smb2_inotify_rename(struct torture_context *torture,
					struct smb2_tree *tree1,
					struct smb2_tree *tree2)
{
	NTSTATUS status;
	struct smb2_notify notify;
	struct notify_changes change1 = {0};
	struct notify_changes change2 = {0};
	struct smb2_create create;
	union smb_setfileinfo sinfo;
	struct smb2_handle h1 = {{0}};
	struct smb2_handle h2 = {{0}};
	struct smb2_request *req;
	struct tevent_timer *te = NULL;
	bool ok = false;

	smb2_deltree(tree1, BASEDIR_INR);

	torture_comment(torture, "Testing change notify of a rename with inotify\n");

	status = torture_smb2_testdir(tree1, BASEDIR_INR, &h1);
	torture_assert_ntstatus_ok_goto(torture, status, ok, done, "torture_smb2_testdir failed");

	ZERO_STRUCT(create);
	create.in.desired_access = SEC_RIGHTS_FILE_READ |
		SEC_RIGHTS_FILE_WRITE|
		SEC_RIGHTS_FILE_ALL;
	create.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	create.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	create.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
		NTCREATEX_SHARE_ACCESS_WRITE |
		NTCREATEX_SHARE_ACCESS_DELETE;
	create.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
	create.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	create.in.fname = BASEDIR_INR "\\subdir-name";

	status = smb2_create(tree2, torture, &create);
	torture_assert_ntstatus_ok_goto(torture, status, ok, done, "smb2_create failed\n");
	h2 = create.out.file.handle;

	ZERO_STRUCT(notify);
	notify.level = RAW_NOTIFY_SMB2;
	notify.in.buffer_size = 4096;
	notify.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.in.file.handle = h1;
	notify.in.recursive = true;
	req = smb2_notify_send(tree1, &notify);
	torture_assert_not_null_goto(torture, req, ok, done, "smb2_notify_send failed\n");

	while (!NT_STATUS_EQUAL(req->status, STATUS_PENDING)) {
		if (tevent_loop_once(torture->ev) != 0) {
			goto done;
		}
	}

	ZERO_STRUCT(sinfo);
	sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION;
	sinfo.rename_information.in.file.handle = h2;
	sinfo.rename_information.in.new_name = BASEDIR_INR "\\subdir-name-r";

	status = smb2_setinfo_file(tree2, &sinfo);
	torture_assert_ntstatus_ok_goto(torture, status, ok, done, "smb2_setinfo_file failed\n");

	smb2_util_close(tree2, h2);

	te = tevent_add_timer(torture->ev,
			      tree1,
			      tevent_timeval_current_ofs(1, 0),
			      notify_timeout,
			      req);
	torture_assert_not_null_goto(torture, te, ok, done, "tevent_add_timer failed\n");

	status = smb2_notify_recv(req, torture, &notify);
	torture_assert_ntstatus_ok_goto(torture, status, ok, done, "smb2_notify_recv failed\n");

	torture_assert_goto(torture, notify.out.num_changes == 1 || notify.out.num_changes == 2,
			    ok, done, "bad notify\n");

	change1 = notify.out.changes[0];
	if (notify.out.num_changes == 2) {
		change2 = notify.out.changes[1];
	} else {
		/*
		 * We may only get one event at a time, so check for the
		 * matching second event for the oldname/newname or
		 * removed/added pair.
		 */
		ZERO_STRUCT(notify);
		notify.level = RAW_NOTIFY_SMB2;
		notify.in.buffer_size = 4096;
		notify.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
		notify.in.file.handle = h1;
		notify.in.recursive = true;
		req = smb2_notify_send(tree1, &notify);
		torture_assert_not_null_goto(torture, req, ok, done, "smb2_notify_send failed\n");

		status = smb2_notify_recv(req, torture, &notify);
		torture_assert_ntstatus_ok_goto(torture, status, ok, done, "smb2_notify_recv failed\n");

		torture_assert_goto(torture, notify.out.num_changes == 1, ok, done,
				    "bad notify\n");

		change2 = notify.out.changes[0];
	}

	if ((change1.action != NOTIFY_ACTION_OLD_NAME) &&
	    (change1.action != NOTIFY_ACTION_REMOVED))
	{
		torture_fail_goto(torture, done, "bad change notification\n");
	}
	torture_assert_str_equal_goto(torture, change1.name.s, "subdir-name",
			    ok, done, "bad change notification\n");

	if ((change2.action != NOTIFY_ACTION_NEW_NAME) &&
	    (change2.action != NOTIFY_ACTION_ADDED))
	{
		torture_fail_goto(torture, done, "bad change notification\n");
	}
	torture_assert_str_equal_goto(torture, change2.name.s, "subdir-name-r",
			    ok, done, "bad change notification\n");

	ok = true;
done:
	if (!smb2_util_handle_empty(h1)) {
		smb2_util_close(tree1, h1);
	}
	if (!smb2_util_handle_empty(h2)) {
		smb2_util_close(tree2, h2);
	}

	smb2_deltree(tree1, BASEDIR_INR);
	return ok;
}


static bool test_valid_request(struct torture_context *torture,
			       struct smb2_tree *tree)
{
	bool ret = true;
	NTSTATUS status;
	struct smb2_handle dh;
	struct smb2_notify n;
	struct smb2_request *req;
	uint32_t max_buffer_size;

	torture_comment(torture, "TESTING VALIDITY OF CHANGE NOTIFY REQUEST\n");

	smb2_transport_credits_ask_num(tree->session->transport, 256);

	smb2_util_unlink(tree, FNAME);

	status = smb2_util_roothandle(tree, &dh);
	CHECK_STATUS(status, NT_STATUS_OK);

	max_buffer_size =
		smb2cli_conn_max_trans_size(tree->session->transport->conn);

	n.in.recursive		= 0x0000;
	n.in.buffer_size	= max_buffer_size;
	n.in.file.handle	= dh;
	n.in.completion_filter	= FILE_NOTIFY_CHANGE_ALL;
	n.in.unknown		= 0x00000000;
	req = smb2_notify_send(tree, &n);

	while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) {
		if (tevent_loop_once(torture->ev) != 0) {
			break;
		}
	}

	status = torture_setup_simple_file(torture, tree, FNAME);
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_notify_recv(req, torture, &n);
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(n.out.num_changes, 1);
	CHECK_VAL(n.out.changes[0].action, NOTIFY_ACTION_ADDED);
	CHECK_WIRE_STR(n.out.changes[0].name, FNAME);

	/*
	 * if the change response doesn't fit in the buffer
	 * NOTIFY_ENUM_DIR is returned.
	 */
	n.in.buffer_size	= 0x00000000;
	req = smb2_notify_send(tree, &n);

	while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) {
		if (tevent_loop_once(torture->ev) != 0) {
			break;
		}
	}

	status = torture_setup_simple_file(torture, tree, FNAME);
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_notify_recv(req, torture, &n);
	CHECK_STATUS(status, STATUS_NOTIFY_ENUM_DIR);

	/*
	 * if the change response fits in the buffer we get
	 * NT_STATUS_OK again
	 */
	n.in.buffer_size	= max_buffer_size;
	req = smb2_notify_send(tree, &n);

	while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) {
		if (tevent_loop_once(torture->ev) != 0) {
			break;
		}
	}

	status = torture_setup_simple_file(torture, tree, FNAME);
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_notify_recv(req, torture, &n);
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(n.out.num_changes, 3);
	CHECK_VAL(n.out.changes[0].action, NOTIFY_ACTION_REMOVED);
	CHECK_WIRE_STR(n.out.changes[0].name, FNAME);
	CHECK_VAL(n.out.changes[1].action, NOTIFY_ACTION_ADDED);
	CHECK_WIRE_STR(n.out.changes[1].name, FNAME);
	CHECK_VAL(n.out.changes[2].action, NOTIFY_ACTION_MODIFIED);
	CHECK_WIRE_STR(n.out.changes[2].name, FNAME);

	/* if the first notify returns NOTIFY_ENUM_DIR, all do */
	status = smb2_util_close(tree, dh);
	CHECK_STATUS(status, NT_STATUS_OK);
	status = smb2_util_roothandle(tree, &dh);
	CHECK_STATUS(status, NT_STATUS_OK);

	n.in.recursive		= 0x0000;
	n.in.buffer_size	= 0x00000001;
	n.in.file.handle	= dh;
	n.in.completion_filter	= FILE_NOTIFY_CHANGE_ALL;
	n.in.unknown		= 0x00000000;
	req = smb2_notify_send(tree, &n);

	while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) {
		if (tevent_loop_once(torture->ev) != 0) {
			break;
		}
	}

	status = torture_setup_simple_file(torture, tree, FNAME);
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_notify_recv(req, torture, &n);
	CHECK_STATUS(status, STATUS_NOTIFY_ENUM_DIR);

	n.in.buffer_size        = max_buffer_size;
	req = smb2_notify_send(tree, &n);
	while (!req->cancel.can_cancel && req->state <= SMB2_REQUEST_RECV) {
		if (tevent_loop_once(torture->ev) != 0) {
			break;
		}
	}

	status = torture_setup_simple_file(torture, tree, FNAME);
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_notify_recv(req, torture, &n);
	CHECK_STATUS(status, STATUS_NOTIFY_ENUM_DIR);

	/* if the buffer size is too large, we get invalid parameter */
	n.in.recursive		= 0x0000;
	n.in.buffer_size	= max_buffer_size + 1;
	n.in.file.handle	= dh;
	n.in.completion_filter	= FILE_NOTIFY_CHANGE_ALL;
	n.in.unknown		= 0x00000000;
	req = smb2_notify_send(tree, &n);
	status = smb2_notify_recv(req, torture, &n);
	CHECK_STATUS(status, NT_STATUS_INVALID_PARAMETER);

done:
	return ret;
}


static bool torture_smb2_notify_mask_change(struct torture_context *torture,
					    struct smb2_tree *tree1,
					    struct smb2_tree *tree2)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io, io1;
	struct smb2_handle h1;
	struct smb2_request *req1, *req2;
	union smb_setfileinfo sinfo;

	smb2_deltree(tree1, BASEDIR_MC);
	smb2_util_rmdir(tree1, BASEDIR_MC);

	torture_comment(torture, "TESTING CHANGE NOTIFY WITH MASK CHANGE\n");

	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_MC;

	status = smb2_create(tree1, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify, on file or directory name
	   changes. Setup both with and without recursion */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_ATTRIBUTES;
	notify.smb2.in.file.handle = h1;

	notify.smb2.in.recursive = true;
	req1 = smb2_notify_send(tree1, &(notify.smb2));

	smb2_cancel(req1);
	status = smb2_notify_recv(req1, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_CANCELLED);

	notify.smb2.in.recursive = false;
	req2 = smb2_notify_send(tree1, &(notify.smb2));

	smb2_cancel(req2);
	status = smb2_notify_recv(req2, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_CANCELLED);

	notify.smb2.in.recursive = true;
	req1 = smb2_notify_send(tree1, &(notify.smb2));

	/* Set to hidden then back again. */
	ZERO_STRUCT(io1.smb2);
	io1.generic.level = RAW_OPEN_SMB2;
	io1.smb2.in.create_flags = 0;
	io1.smb2.in.desired_access = SEC_RIGHTS_FILE_READ |
				SEC_RIGHTS_FILE_WRITE|
				SEC_RIGHTS_FILE_ALL;
	io1.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io1.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE |
				NTCREATEX_SHARE_ACCESS_DELETE;
	io1.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io1.smb2.in.security_flags = 0;
	io1.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE;
	io1.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io1.smb2.in.fname = BASEDIR_MC "\\tname1";

	smb2_util_close(tree1,
		custom_smb2_create(tree1, torture, &(io1.smb2)));
	status = smb2_util_setatr(tree1, BASEDIR_MC "\\tname1",
				FILE_ATTRIBUTE_HIDDEN);
	CHECK_STATUS(status, NT_STATUS_OK);
	smb2_util_unlink(tree1, BASEDIR_MC "\\tname1");

	status = smb2_notify_recv(req1, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_MODIFIED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "tname1");

	/* Now try and change the mask to include other events.
	 * This should not work - once the mask is set on a directory
	 * h1 it seems to be fixed until the fnum is closed. */

	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME |
					FILE_NOTIFY_CHANGE_ATTRIBUTES |
					FILE_NOTIFY_CHANGE_CREATION;
	notify.smb2.in.recursive = true;
	req1 = smb2_notify_send(tree1, &(notify.smb2));

	notify.smb2.in.recursive = false;
	req2 = smb2_notify_send(tree1, &(notify.smb2));

	io1.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io1.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io1.smb2.in.fname = BASEDIR_MC "\\subdir-name";
	status = smb2_create(tree2, torture, &(io1.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	smb2_util_close(tree2, io1.smb2.out.file.handle);

	ZERO_STRUCT(sinfo);
	io1.smb2.in.fname = BASEDIR_MC "\\subdir-name\\subname1";
	io1.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io1.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	status = smb2_create(tree2, torture, &(io1.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	sinfo.rename_information.level = RAW_SFILEINFO_RENAME_INFORMATION;
	sinfo.rename_information.in.file.handle = io1.smb2.out.file.handle;
	sinfo.rename_information.in.overwrite = true;
	sinfo.rename_information.in.root_fid = 0;
	sinfo.rename_information.in.new_name =
				BASEDIR_MC "\\subdir-name\\subname1-r";
	status = smb2_setinfo_file(tree2, &sinfo);
	CHECK_STATUS(status, NT_STATUS_OK);

	io1.smb2.in.fname = BASEDIR_MC "\\subdir-name\\subname2";
	io1.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io1.smb2.in.create_options = NTCREATEX_OPTIONS_NON_DIRECTORY_FILE;
	status = smb2_create(tree2, torture, &(io1.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	sinfo.rename_information.in.file.handle = io1.smb2.out.file.handle;
	sinfo.rename_information.in.new_name = BASEDIR_MC "\\subname2-r";
	status = smb2_setinfo_file(tree2, &sinfo);
	CHECK_STATUS(status, NT_STATUS_OK);
	smb2_util_close(tree2, io1.smb2.out.file.handle);

	io1.smb2.in.fname = BASEDIR_MC "\\subname2-r";
	io1.smb2.in.create_disposition = NTCREATEX_DISP_OPEN;
	status = smb2_create(tree2, torture, &(io1.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	sinfo.rename_information.in.file.handle = io1.smb2.out.file.handle;
	sinfo.rename_information.in.new_name = BASEDIR_MC "\\subname3-r";
	status = smb2_setinfo_file(tree2, &sinfo);
	CHECK_STATUS(status, NT_STATUS_OK);
	smb2_util_close(tree2, io1.smb2.out.file.handle);

	status = smb2_util_rmdir(tree2, BASEDIR_MC "\\subdir-name\\subname1-r");
	CHECK_STATUS(status, NT_STATUS_OK);
	status = smb2_util_rmdir(tree2, BASEDIR_MC "\\subdir-name");
	CHECK_STATUS(status, NT_STATUS_OK);
	status = smb2_util_unlink(tree2, BASEDIR_MC "\\subname3-r");
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_notify_recv(req1, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_MODIFIED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subname2-r");

	status = smb2_notify_recv(req2, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_MODIFIED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subname3-r");

	if (!ret) {
		goto done;
	}

done:
	smb2_deltree(tree1, BASEDIR_MC);
	return ret;
}


static bool torture_smb2_notify_rmdir4(struct torture_context *torture,
				       struct smb2_tree *tree1,
				       struct smb2_tree *tree2)
{
	return torture_smb2_notify_rmdir(torture, tree1, tree2, true);
}


static bool torture_smb2_notify_tcon(struct torture_context *torture,
				  struct smb2_tree *tree)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	struct smb2_handle h1 = {{0}};
	struct smb2_request *req = NULL;
	struct smb2_tree *tree1 = NULL;
	const char *fname = BASEDIR_TCON "\\subdir-name";

	smb2_deltree(tree, BASEDIR_TCON);
	smb2_util_rmdir(tree, BASEDIR_TCON);

	torture_comment(torture, "TESTING SIMPLE CHANGE NOTIFY\n");

	/*
	  get a handle on the directory
	*/

	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_RIGHTS_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL |
				FILE_ATTRIBUTE_DIRECTORY;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_OPEN_IF;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_TCON;

	status = smb2_create(tree, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify,
	   on file or directory name changes */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.recursive = true;

	torture_comment(torture, "Testing notify mkdir\n");
	req = smb2_notify_send(tree, &(notify.smb2));
	smb2_cancel(req);
	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_CANCELLED);

	notify.smb2.in.recursive = true;
	req = smb2_notify_send(tree, &(notify.smb2));
	status = smb2_util_mkdir(tree, fname);
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_ADDED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name");

	torture_comment(torture, "Testing notify rmdir\n");
	req = smb2_notify_send(tree, &(notify.smb2));
	status = smb2_util_rmdir(tree, fname);
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_REMOVED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name");

	torture_comment(torture, "SIMPLE CHANGE NOTIFY OK\n");

	torture_comment(torture, "TESTING WITH SECONDARY TCON\n");
	if (!torture_smb2_tree_connect(torture, tree->session, tree, &tree1)) {
		torture_warning(torture, "couldn't reconnect to share, bailing\n");
		ret = false;
		goto done;
	}

	torture_comment(torture, "tid1=%d tid2=%d\n",
			smb2cli_tcon_current_id(tree->smbXcli),
			smb2cli_tcon_current_id(tree1->smbXcli));

	torture_comment(torture, "Testing notify mkdir\n");
	req = smb2_notify_send(tree, &(notify.smb2));
	smb2_util_mkdir(tree1, fname);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_ADDED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name");

	torture_comment(torture, "Testing notify rmdir\n");
	req = smb2_notify_send(tree, &(notify.smb2));
	smb2_util_rmdir(tree, fname);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_REMOVED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name");

	torture_comment(torture, "CHANGE NOTIFY WITH TCON OK\n");

	torture_comment(torture, "Disconnecting secondary tree\n");
	status = smb2_tdis(tree1);
	CHECK_STATUS(status, NT_STATUS_OK);
	talloc_free(tree1);

	torture_comment(torture, "Testing notify mkdir\n");
	req = smb2_notify_send(tree, &(notify.smb2));
	smb2_util_mkdir(tree, fname);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);

	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_ADDED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name");

	torture_comment(torture, "Testing notify rmdir\n");
	req = smb2_notify_send(tree, &(notify.smb2));
	smb2_util_rmdir(tree, fname);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	CHECK_VAL(notify.smb2.out.num_changes, 1);
	CHECK_VAL(notify.smb2.out.changes[0].action, NOTIFY_ACTION_REMOVED);
	CHECK_WIRE_STR(notify.smb2.out.changes[0].name, "subdir-name");

	torture_comment(torture, "CHANGE NOTIFY WITH TDIS OK\n");
done:
	smb2_util_close(tree, h1);
	smb2_deltree(tree, BASEDIR_TCON);

	return ret;
}


static bool torture_smb2_notify_tree_disconnect_1(
		struct torture_context *torture,
		struct smb2_tree *tree)
{
	bool ret = true;
	NTSTATUS status;
	union smb_notify notify;
	union smb_open io;
	struct smb2_handle h1;
	struct smb2_request *req;

	smb2_deltree(tree, BASEDIR_NTDIS);
	smb2_util_rmdir(tree, BASEDIR_NTDIS);

	torture_comment(torture, "TESTING CHANGE NOTIFY ASYNC FOLLOWED BY "
			"TREE-DISCONNECT\n");

	/*
	  get a handle on the directory
	*/
	ZERO_STRUCT(io.smb2);
	io.generic.level = RAW_OPEN_SMB2;
	io.smb2.in.create_flags = 0;
	io.smb2.in.desired_access = SEC_FILE_ALL;
	io.smb2.in.create_options = NTCREATEX_OPTIONS_DIRECTORY;
	io.smb2.in.file_attributes = FILE_ATTRIBUTE_NORMAL;
	io.smb2.in.share_access = NTCREATEX_SHARE_ACCESS_READ |
				NTCREATEX_SHARE_ACCESS_WRITE;
	io.smb2.in.alloc_size = 0;
	io.smb2.in.create_disposition = NTCREATEX_DISP_CREATE;
	io.smb2.in.impersonation_level = SMB2_IMPERSONATION_ANONYMOUS;
	io.smb2.in.security_flags = 0;
	io.smb2.in.fname = BASEDIR_NTDIS;

	status = smb2_create(tree, torture, &(io.smb2));
	CHECK_STATUS(status, NT_STATUS_OK);
	h1 = io.smb2.out.file.handle;

	/* ask for a change notify,
	   on file or directory name changes */
	ZERO_STRUCT(notify.smb2);
	notify.smb2.level = RAW_NOTIFY_SMB2;
	notify.smb2.in.buffer_size = 1000;
	notify.smb2.in.completion_filter = FILE_NOTIFY_CHANGE_NAME;
	notify.smb2.in.file.handle = h1;
	notify.smb2.in.recursive = true;

	req = smb2_notify_send(tree, &(notify.smb2));
	WAIT_FOR_ASYNC_RESPONSE(req);

	status = smb2_tdis(tree);
	CHECK_STATUS(status, NT_STATUS_OK);

	status = smb2_notify_recv(req, torture, &(notify.smb2));
	CHECK_STATUS(status, STATUS_NOTIFY_CLEANUP);
	CHECK_VAL(notify.smb2.out.num_changes, 0);

done:
	smb2_deltree(tree, BASEDIR_NTDIS);
	return ret;
}


fixed another unsigned integer overflow

first fixed by google in android fork,
https://android.googlesource.com/platform/external/libexif/+/1e187b62682ffab5003c702657d6d725b4278f16%5E%21/#F0

(use a more generic overflow check method, also check second overflow instance.)

https://security-tracker.debian.org/tracker/CVE-2020-0198

exif_data_load_data_thumbnail (ExifData *data, const unsigned char *d,
			       unsigned int ds, ExifLong o, ExifLong s)
{
	/* Sanity checks */
	if (o >= ds) {
		exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "Bogus thumbnail offset (%u).", o);
		return;
	}
	if (CHECKOVERFLOW(o,ds,s)) {
		exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData", "Bogus thumbnail size (%u), max would be %u.", s, ds-o);
		return;
	}
	if (data->data)
		exif_mem_free (data->priv->mem, data->data);
	if (!(data->data = exif_data_alloc (data, s))) {
		EXIF_LOG_NO_MEMORY (data->priv->log, "ExifData", s);
		data->size = 0;
		return;
	}
	data->size = s;
	memcpy (data->data, d + o, s);
}

In exif_data_load_data_thumbnail of exif-data.c, there is a possible denial of service due to an integer overflow. This could lead to remote denial of service with no additional execution privileges needed. User interaction is not needed for exploitation.Product: AndroidVersions: Android-10Android ID: A-145075076


exif_data_load_data_content (ExifData *data, ExifIfd ifd,
			     const unsigned char *d,
			     unsigned int ds, unsigned int offset, unsigned int recursion_cost)
{
	ExifLong o, thumbnail_offset = 0, thumbnail_length = 0;
	ExifShort n;
	ExifEntry *entry;
	unsigned int i;
	ExifTag tag;

	if (!data || !data->priv)
		return;

	/* check for valid ExifIfd enum range */
	if ((((int)ifd) < 0) || ( ((int)ifd) >= EXIF_IFD_COUNT))
	  return;

	if (recursion_cost > 170) {
		/*
		 * recursion_cost is a logarithmic-scale indicator of how expensive this
		 * recursive call might end up being. It is an indicator of the depth of
		 * recursion as well as the potential for worst-case future recursive
		 * calls. Since it's difficult to tell ahead of time how often recursion
		 * will occur, this assumes the worst by assuming every tag could end up
		 * causing recursion.
		 * The value of 170 was chosen to limit typical EXIF structures to a
		 * recursive depth of about 6, but pathological ones (those with very
		 * many tags) to only 2.
		 */
		exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "ExifData",
			  "Deep/expensive recursion detected!");
		return;
	}

	/* Read the number of entries */
	if (CHECKOVERFLOW(offset, ds, 2)) {
		exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "ExifData",
			  "Tag data past end of buffer (%u+2 > %u)", offset, ds);
		return;
	}
	n = exif_get_short (d + offset, data->priv->order);
	exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
	          "Loading %hu entries...", n);
	offset += 2;

	/* Check if we have enough data. */
	if (CHECKOVERFLOW(offset, ds, 12*n)) {
		n = (ds - offset) / 12;
		exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
				  "Short data; only loading %hu entries...", n);
	}

	for (i = 0; i < n; i++) {

		tag = exif_get_short (d + offset + 12 * i, data->priv->order);
		switch (tag) {
		case EXIF_TAG_EXIF_IFD_POINTER:
		case EXIF_TAG_GPS_INFO_IFD_POINTER:
		case EXIF_TAG_INTEROPERABILITY_IFD_POINTER:
		case EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
		case EXIF_TAG_JPEG_INTERCHANGE_FORMAT:
			o = exif_get_long (d + offset + 12 * i + 8,
					   data->priv->order);
			if (o >= ds) {
				exif_log (data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "ExifData",
					  "Tag data past end of buffer (%u > %u)", offset+2, ds);
				return;
			}
			/* FIXME: IFD_POINTER tags aren't marked as being in a
			 * specific IFD, so exif_tag_get_name_in_ifd won't work
			 */
			exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
				  "Sub-IFD entry 0x%x ('%s') at %u.", tag,
				  exif_tag_get_name(tag), o);
			switch (tag) {
			case EXIF_TAG_EXIF_IFD_POINTER:
				CHECK_REC (EXIF_IFD_EXIF);
				exif_data_load_data_content (data, EXIF_IFD_EXIF, d, ds, o,
					recursion_cost + level_cost(n));
				break;
			case EXIF_TAG_GPS_INFO_IFD_POINTER:
				CHECK_REC (EXIF_IFD_GPS);
				exif_data_load_data_content (data, EXIF_IFD_GPS, d, ds, o,
					recursion_cost + level_cost(n));
				break;
			case EXIF_TAG_INTEROPERABILITY_IFD_POINTER:
				CHECK_REC (EXIF_IFD_INTEROPERABILITY);
				exif_data_load_data_content (data, EXIF_IFD_INTEROPERABILITY, d, ds, o,
					recursion_cost + level_cost(n));
				break;
			case EXIF_TAG_JPEG_INTERCHANGE_FORMAT:
				thumbnail_offset = o;
				if (thumbnail_offset && thumbnail_length)
					exif_data_load_data_thumbnail (data, d,
								       ds, thumbnail_offset,
								       thumbnail_length);
				break;
			case EXIF_TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
				thumbnail_length = o;
				if (thumbnail_offset && thumbnail_length)
					exif_data_load_data_thumbnail (data, d,
								       ds, thumbnail_offset,
								       thumbnail_length);
				break;
			default:
				return;
			}
			break;
		default:

			/*
			 * If we don't know the tag, don't fail. It could be that new
			 * versions of the standard have defined additional tags. Note that
			 * 0 is a valid tag in the GPS IFD.
			 */
			if (!exif_tag_get_name_in_ifd (tag, ifd)) {

				/*
				 * Special case: Tag and format 0. That's against specification
				 * (at least up to 2.2). But Photoshop writes it anyways.
				 */
				if (!memcmp (d + offset + 12 * i, "\0\0\0\0", 4)) {
					exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
						  "Skipping empty entry at position %u in '%s'.", i,
						  exif_ifd_get_name (ifd));
					break;
				}
				exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
					  "Unknown tag 0x%04x (entry %u in '%s'). Please report this tag "
					  "to <libexif-devel@lists.sourceforge.net>.", tag, i,
					  exif_ifd_get_name (ifd));
				if (data->priv->options & EXIF_DATA_OPTION_IGNORE_UNKNOWN_TAGS)
					break;
			}
			entry = exif_entry_new_mem (data->priv->mem);
			if (!entry) {
				  exif_log (data->priv->log, EXIF_LOG_CODE_NO_MEMORY, "ExifData",
                                          "Could not allocate memory");
				  return;
			}
			if (exif_data_load_data_entry (data, entry, d, ds,
						   offset + 12 * i))
				exif_content_add_entry (data->ifd[ifd], entry);
			exif_entry_unref (entry);
			break;
		}
	}
}


KVM: do not allow mapping valid but non-reference-counted pages

It's possible to create a region which maps valid but non-refcounted
pages (e.g., tail pages of non-compound higher order allocations). These
host pages can then be returned by gfn_to_page, gfn_to_pfn, etc., family
of APIs, which take a reference to the page, which takes it from 0 to 1.
When the reference is dropped, this will free the page incorrectly.

Fix this by only taking a reference on valid pages if it was non-zero,
which indicates it is participating in normal refcounting (and can be
released with put_page).

This addresses CVE-2021-22543.

Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Tested-by: Paolo Bonzini <pbonzini@redhat.com>
Cc: stable@vger.kernel.org
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

static int kvm_mmu_notifier_invalidate_range_start(struct mmu_notifier *mn,
					const struct mmu_notifier_range *range)
{
	struct kvm *kvm = mmu_notifier_to_kvm(mn);
	const struct kvm_hva_range hva_range = {
		.start		= range->start,
		.end		= range->end,
		.pte		= __pte(0),
		.handler	= kvm_unmap_gfn_range,
		.on_lock	= kvm_inc_notifier_count,
		.flush_on_ret	= true,
		.may_block	= mmu_notifier_range_blockable(range),
	};

	trace_kvm_unmap_hva_range(range->start, range->end);

	__kvm_handle_hva_range(kvm, &hva_range);

	return 0;
}

An issue was discovered in Linux: KVM through Improper handling of VM_IO|VM_PFNMAP vmas in KVM can bypass RO checks and can lead to pages being freed while still accessible by the VMM and guest. This allows users with the ability to start and control a VM to read/write random pages of memory and can result in local privilege escalation.


static void kvm_mmu_notifier_invalidate_range_end(struct mmu_notifier *mn,
					const struct mmu_notifier_range *range)
{
	struct kvm *kvm = mmu_notifier_to_kvm(mn);
	const struct kvm_hva_range hva_range = {
		.start		= range->start,
		.end		= range->end,
		.pte		= __pte(0),
		.handler	= (void *)kvm_null_fn,
		.on_lock	= kvm_dec_notifier_count,
		.flush_on_ret	= false,
		.may_block	= mmu_notifier_range_blockable(range),
	};

	__kvm_handle_hva_range(kvm, &hva_range);

	BUG_ON(kvm->mmu_notifier_count < 0);
}


static void check_processor_compat(void *data)
{
	struct kvm_cpu_compat_check *c = data;

	*c->ret = kvm_arch_check_processor_compat(c->opaque);
}


static long kvm_vm_ioctl(struct file *filp,
			   unsigned int ioctl, unsigned long arg)
{
	struct kvm *kvm = filp->private_data;
	void __user *argp = (void __user *)arg;
	int r;

	if (kvm->mm != current->mm)
		return -EIO;
	switch (ioctl) {
	case KVM_CREATE_VCPU:
		r = kvm_vm_ioctl_create_vcpu(kvm, arg);
		break;
	case KVM_ENABLE_CAP: {
		struct kvm_enable_cap cap;

		r = -EFAULT;
		if (copy_from_user(&cap, argp, sizeof(cap)))
			goto out;
		r = kvm_vm_ioctl_enable_cap_generic(kvm, &cap);
		break;
	}
	case KVM_SET_USER_MEMORY_REGION: {
		struct kvm_userspace_memory_region kvm_userspace_mem;

		r = -EFAULT;
		if (copy_from_user(&kvm_userspace_mem, argp,
						sizeof(kvm_userspace_mem)))
			goto out;

		r = kvm_vm_ioctl_set_memory_region(kvm, &kvm_userspace_mem);
		break;
	}
	case KVM_GET_DIRTY_LOG: {
		struct kvm_dirty_log log;

		r = -EFAULT;
		if (copy_from_user(&log, argp, sizeof(log)))
			goto out;
		r = kvm_vm_ioctl_get_dirty_log(kvm, &log);
		break;
	}
#ifdef CONFIG_KVM_GENERIC_DIRTYLOG_READ_PROTECT
	case KVM_CLEAR_DIRTY_LOG: {
		struct kvm_clear_dirty_log log;

		r = -EFAULT;
		if (copy_from_user(&log, argp, sizeof(log)))
			goto out;
		r = kvm_vm_ioctl_clear_dirty_log(kvm, &log);
		break;
	}
#endif
#ifdef CONFIG_KVM_MMIO
	case KVM_REGISTER_COALESCED_MMIO: {
		struct kvm_coalesced_mmio_zone zone;

		r = -EFAULT;
		if (copy_from_user(&zone, argp, sizeof(zone)))
			goto out;
		r = kvm_vm_ioctl_register_coalesced_mmio(kvm, &zone);
		break;
	}
	case KVM_UNREGISTER_COALESCED_MMIO: {
		struct kvm_coalesced_mmio_zone zone;

		r = -EFAULT;
		if (copy_from_user(&zone, argp, sizeof(zone)))
			goto out;
		r = kvm_vm_ioctl_unregister_coalesced_mmio(kvm, &zone);
		break;
	}
#endif
	case KVM_IRQFD: {
		struct kvm_irqfd data;

		r = -EFAULT;
		if (copy_from_user(&data, argp, sizeof(data)))
			goto out;
		r = kvm_irqfd(kvm, &data);
		break;
	}
	case KVM_IOEVENTFD: {
		struct kvm_ioeventfd data;

		r = -EFAULT;
		if (copy_from_user(&data, argp, sizeof(data)))
			goto out;
		r = kvm_ioeventfd(kvm, &data);
		break;
	}
#ifdef CONFIG_HAVE_KVM_MSI
	case KVM_SIGNAL_MSI: {
		struct kvm_msi msi;

		r = -EFAULT;
		if (copy_from_user(&msi, argp, sizeof(msi)))
			goto out;
		r = kvm_send_userspace_msi(kvm, &msi);
		break;
	}
#endif
#ifdef __KVM_HAVE_IRQ_LINE
	case KVM_IRQ_LINE_STATUS:
	case KVM_IRQ_LINE: {
		struct kvm_irq_level irq_event;

		r = -EFAULT;
		if (copy_from_user(&irq_event, argp, sizeof(irq_event)))
			goto out;

		r = kvm_vm_ioctl_irq_line(kvm, &irq_event,
					ioctl == KVM_IRQ_LINE_STATUS);
		if (r)
			goto out;

		r = -EFAULT;
		if (ioctl == KVM_IRQ_LINE_STATUS) {
			if (copy_to_user(argp, &irq_event, sizeof(irq_event)))
				goto out;
		}

		r = 0;
		break;
	}
#endif
#ifdef CONFIG_HAVE_KVM_IRQ_ROUTING
	case KVM_SET_GSI_ROUTING: {
		struct kvm_irq_routing routing;
		struct kvm_irq_routing __user *urouting;
		struct kvm_irq_routing_entry *entries = NULL;

		r = -EFAULT;
		if (copy_from_user(&routing, argp, sizeof(routing)))
			goto out;
		r = -EINVAL;
		if (!kvm_arch_can_set_irq_routing(kvm))
			goto out;
		if (routing.nr > KVM_MAX_IRQ_ROUTES)
			goto out;
		if (routing.flags)
			goto out;
		if (routing.nr) {
			urouting = argp;
			entries = vmemdup_user(urouting->entries,
					       array_size(sizeof(*entries),
							  routing.nr));
			if (IS_ERR(entries)) {
				r = PTR_ERR(entries);
				goto out;
			}
		}
		r = kvm_set_irq_routing(kvm, entries, routing.nr,
					routing.flags);
		kvfree(entries);
		break;
	}
#endif /* CONFIG_HAVE_KVM_IRQ_ROUTING */
	case KVM_CREATE_DEVICE: {
		struct kvm_create_device cd;

		r = -EFAULT;
		if (copy_from_user(&cd, argp, sizeof(cd)))
			goto out;

		r = kvm_ioctl_create_device(kvm, &cd);
		if (r)
			goto out;

		r = -EFAULT;
		if (copy_to_user(argp, &cd, sizeof(cd)))
			goto out;

		r = 0;
		break;
	}
	case KVM_CHECK_EXTENSION:
		r = kvm_vm_ioctl_check_extension_generic(kvm, arg);
		break;
	case KVM_RESET_DIRTY_RINGS:
		r = kvm_vm_ioctl_reset_dirty_pages(kvm);
		break;
	default:
		r = kvm_arch_vm_ioctl(filp, ioctl, arg);
	}
out:
	return r;
}


static void kvm_inc_notifier_count(struct kvm *kvm, unsigned long start,
				   unsigned long end)
{
	/*
	 * The count increase must become visible at unlock time as no
	 * spte can be established without taking the mmu_lock and
	 * count is also read inside the mmu_lock critical section.
	 */
	kvm->mmu_notifier_count++;
	if (likely(kvm->mmu_notifier_count == 1)) {
		kvm->mmu_notifier_range_start = start;
		kvm->mmu_notifier_range_end = end;
	} else {
		/*
		 * Fully tracking multiple concurrent ranges has dimishing
		 * returns. Keep things simple and just find the minimal range
		 * which includes the current and new ranges. As there won't be
		 * enough information to subtract a range after its invalidate
		 * completes, any ranges invalidated concurrently will
		 * accumulate and persist until all outstanding invalidates
		 * complete.
		 */
		kvm->mmu_notifier_range_start =
			min(kvm->mmu_notifier_range_start, start);
		kvm->mmu_notifier_range_end =
			max(kvm->mmu_notifier_range_end, end);
	}
}


static int kvm_vm_ioctl_enable_dirty_log_ring(struct kvm *kvm, u32 size)
{
	int r;

	if (!KVM_DIRTY_LOG_PAGE_OFFSET)
		return -EINVAL;

	/* the size should be power of 2 */
	if (!size || (size & (size - 1)))
		return -EINVAL;

	/* Should be bigger to keep the reserved entries, or a page */
	if (size < kvm_dirty_ring_get_rsvd_entries() *
	    sizeof(struct kvm_dirty_gfn) || size < PAGE_SIZE)
		return -EINVAL;

	if (size > KVM_DIRTY_RING_MAX_ENTRIES *
	    sizeof(struct kvm_dirty_gfn))
		return -E2BIG;

	/* We only allow it to set once */
	if (kvm->dirty_ring_size)
		return -EINVAL;

	mutex_lock(&kvm->lock);

	if (kvm->created_vcpus) {
		/* We don't allow to change this value after vcpu created */
		r = -EINVAL;
	} else {
		kvm->dirty_ring_size = size;
		r = 0;
	}

	mutex_unlock(&kvm->lock);
	return r;
}


static kvm_pfn_t hva_to_pfn(unsigned long addr, bool atomic, bool *async,
			bool write_fault, bool *writable)
{
	struct vm_area_struct *vma;
	kvm_pfn_t pfn = 0;
	int npages, r;

	/* we can do it either atomically or asynchronously, not both */
	BUG_ON(atomic && async);

	if (hva_to_pfn_fast(addr, write_fault, writable, &pfn))
		return pfn;

	if (atomic)
		return KVM_PFN_ERR_FAULT;

	npages = hva_to_pfn_slow(addr, async, write_fault, writable, &pfn);
	if (npages == 1)
		return pfn;

	mmap_read_lock(current->mm);
	if (npages == -EHWPOISON ||
	      (!async && check_user_page_hwpoison(addr))) {
		pfn = KVM_PFN_ERR_HWPOISON;
		goto exit;
	}

retry:
	vma = find_vma_intersection(current->mm, addr, addr + 1);

	if (vma == NULL)
		pfn = KVM_PFN_ERR_FAULT;
	else if (vma->vm_flags & (VM_IO | VM_PFNMAP)) {
		r = hva_to_pfn_remapped(vma, addr, async, write_fault, writable, &pfn);
		if (r == -EAGAIN)
			goto retry;
		if (r < 0)
			pfn = KVM_PFN_ERR_FAULT;
	} else {
		if (async && vma_is_valid(vma, write_fault))
			*async = true;
		pfn = KVM_PFN_ERR_FAULT;
	}
exit:
	mmap_read_unlock(current->mm);
	return pfn;
}