[PATCH] ksmbd: implement SMB2 AppInstanceVersion takeover

Namjae Jeon <[email protected]>
Newsgroups org.kernel.vger.linux-cifs
Message-ID <[email protected]>
BVT_AppInstanceVersion_SMB311_GreaterVersion,
BVT_AppInstanceVersion_SMB311_SameVersion,
BVT_AppInstanceVersion_SMB311_LowerAppInstanceVersionHigh, and
BVT_AppInstanceVersion_SMB311_LowerAppInstanceVersionLow exercise
ordered opens using the same AppInstanceId. ksmbd tracked the
AppInstanceId, but did not parse the version context or enforce the
version ordering, so versioned opens returned incorrect sharing
violations.

Parse and retain the 24-byte AppInstanceVersion context with each open.
Reject a version that is lower than or equal to the active version with
STATUS_FILE_FORCED_CLOSED, reject an unversioned open against a versioned
handle, and close the previous handle for a newer takeover. Do not apply
the takeover check to durable reconnect or replay requests.

Signed-off-by: Namjae Jeon <[email protected]>
---
 fs/smb/server/smb2pdu.c   | 98 +++++++++++++++++++++++++++++++++++++--
 fs/smb/server/vfs_cache.c |  7 ++-
 fs/smb/server/vfs_cache.h |  5 ++
 3 files changed, 101 insertions(+), 9 deletions(-)

diff --git a/fs/smb/server/smb2pdu.c b/fs/smb/server/smb2pdu.c
index bd806dca9dcd..da461535229a 100644
--- a/fs/smb/server/smb2pdu.c
+++ b/fs/smb/server/smb2pdu.c
@@ -16,6 +16,7 @@
 #include <linux/filelock.h>
 #include <linux/fileattr.h>
 #include <linux/timekeeping.h>
+#include <linux/unaligned.h>
 
 #include "glob.h"
 #include "../common/smbfsctl.h"
@@ -3451,9 +3452,12 @@ struct durable_info {
 	bool replay;
 	bool replay_consumed;
 	bool app_instance_id;
+	bool app_instance_version_valid;
 	unsigned int timeout;
 	char *CreateGuid;
 	char AppInstanceId[SMB2_CREATE_GUID_SIZE];
+	u64 app_instance_version_high;
+	u64 app_instance_version_low;
 };
 
 static int smb2_check_durable_replay(struct ksmbd_work *work,
@@ -3819,6 +3823,68 @@ static int parse_app_instance_id(struct smb2_create_req *req,
 	return 0;
 }
 
+static int parse_app_instance_version(struct smb2_create_req *req,
+				      struct durable_info *dh_info)
+{
+	struct create_context *context;
+	char *data;
+
+	context = smb2_find_context_vals(req, SMB2_CREATE_APP_INSTANCE_VERSION,
+					 SMB2_CREATE_GUID_SIZE);
+	if (IS_ERR(context))
+		return PTR_ERR(context);
+	if (!context)
+		return 0;
+
+	if (le32_to_cpu(context->DataLength) < 24)
+		return -EINVAL;
+
+	data = (char *)context + le16_to_cpu(context->DataOffset);
+	if (get_unaligned_le16(data) != 24 ||
+	    get_unaligned_le16(data + 2) != 0)
+		return -EINVAL;
+
+	dh_info->app_instance_version_high = get_unaligned_le64(data + 8);
+	dh_info->app_instance_version_low = get_unaligned_le64(data + 16);
+	dh_info->app_instance_version_valid = true;
+	return 0;
+}
+
+static int smb2_handle_app_instance_id(struct smb2_create_rsp *rsp,
+				       struct durable_info *dh_info)
+{
+	struct ksmbd_file *old_fp;
+	bool reject = false;
+
+	if (!dh_info->app_instance_id)
+		return 0;
+
+	old_fp = ksmbd_lookup_fd_app_instance_id(dh_info->AppInstanceId);
+	if (!old_fp)
+		return 0;
+
+	if (dh_info->app_instance_version_valid) {
+		if (old_fp->app_instance_version_valid &&
+		    (dh_info->app_instance_version_high <
+			 old_fp->app_instance_version_high ||
+		     (dh_info->app_instance_version_high ==
+			      old_fp->app_instance_version_high &&
+			      dh_info->app_instance_version_low <=
+			      old_fp->app_instance_version_low)))
+			reject = true;
+	} else if (old_fp->app_instance_version_valid) {
+		reject = true;
+	}
+
+	ksmbd_put_durable_fd(old_fp);
+	if (reject) {
+		rsp->hdr.Status = STATUS_FILE_FORCED_CLOSED;
+		return -EIO;
+	}
+
+	return ksmbd_close_fd_app_instance_id(dh_info->AppInstanceId);
+}
+
 /**
  * smb2_open() - handler for smb file open request
  * @work:	smb work containing request buffer
@@ -3946,6 +4012,15 @@ int smb2_open(struct ksmbd_work *work)
 
 	req_op_level = req->RequestedOplockLevel;
 
+	if (req->CreateContextsOffset) {
+		rc = parse_app_instance_id(req, &dh_info);
+		if (rc)
+			goto err_out2;
+		rc = parse_app_instance_version(req, &dh_info);
+		if (rc)
+			goto err_out2;
+	}
+
 	if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE &&
 	    req->CreateContextsOffset) {
 		lc = parse_lease_state(req);
@@ -3960,9 +4035,6 @@ int smb2_open(struct ksmbd_work *work)
 			if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE)
 				req_op_level = SMB2_OPLOCK_LEVEL_NONE;
 		}
-		rc = parse_app_instance_id(req, &dh_info);
-		if (rc)
-			goto err_out2;
 		rc = parse_durable_handle_context(work, req, lc, &dh_info);
 		if (rc) {
 			ksmbd_debug(SMB, "error parsing durable handle context\n");
@@ -4010,8 +4082,6 @@ int smb2_open(struct ksmbd_work *work)
 			goto reconnected_fp;
 		}
 
-		if (dh_info.type == DURABLE_REQ_V2 && dh_info.app_instance_id)
-			ksmbd_close_fd_app_instance_id(dh_info.AppInstanceId);
 	} else if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE) {
 		lc = parse_lease_state(req);
 		if (IS_ERR(lc)) {
@@ -4026,6 +4096,13 @@ int smb2_open(struct ksmbd_work *work)
 		}
 	}
 
+	if (dh_info.app_instance_id && !dh_info.reconnected &&
+	    !dh_info.replay) {
+		rc = smb2_handle_app_instance_id(rsp, &dh_info);
+		if (rc)
+			goto err_out2;
+	}
+
 	if (le32_to_cpu(req->ImpersonationLevel) > le32_to_cpu(IL_DELEGATE)) {
 		pr_err("Invalid impersonationlevel : 0x%x\n",
 		       le32_to_cpu(req->ImpersonationLevel));
@@ -4426,6 +4503,17 @@ int smb2_open(struct ksmbd_work *work)
 	 * waiting on the same break again.
 	 */
 	memcpy(fp->client_guid, conn->ClientGUID, SMB2_CLIENT_GUID_SIZE);
+	if (dh_info.app_instance_id) {
+		memcpy(fp->app_instance_id, dh_info.AppInstanceId,
+		       SMB2_CREATE_GUID_SIZE);
+		fp->has_app_instance_id = true;
+	}
+	if (dh_info.app_instance_version_valid) {
+		fp->app_instance_version_high =
+			dh_info.app_instance_version_high;
+		fp->app_instance_version_low = dh_info.app_instance_version_low;
+		fp->app_instance_version_valid = true;
+	}
 	if (dh_info.CreateGuid) {
 		memcpy(fp->create_guid, dh_info.CreateGuid, SMB2_CREATE_GUID_SIZE);
 		fp->durable_replay_consumed = dh_info.replay_consumed;
diff --git a/fs/smb/server/vfs_cache.c b/fs/smb/server/vfs_cache.c
index 972e8985a503..413997f393f0 100644
--- a/fs/smb/server/vfs_cache.c
+++ b/fs/smb/server/vfs_cache.c
@@ -995,16 +995,15 @@ bool ksmbd_has_other_active_fd(struct ksmbd_file *fp)
 	return ret;
 }
 
-static struct ksmbd_file *ksmbd_lookup_fd_app_instance_id(char *app_instance_id)
+struct ksmbd_file *ksmbd_lookup_fd_app_instance_id(char *app_instance_id)
 {
 	struct ksmbd_file *fp = NULL;
 	unsigned int id;
 
-	if (!memchr_inv(app_instance_id, 0, SMB2_CREATE_GUID_SIZE))
-		return NULL;
-
 	read_lock(&global_ft.lock);
 	idr_for_each_entry(global_ft.idr, fp, id) {
+		if (!fp->has_app_instance_id)
+			continue;
 		if (!memcmp(fp->app_instance_id, app_instance_id,
 			    SMB2_CREATE_GUID_SIZE)) {
 			fp = ksmbd_fp_get(fp);
diff --git a/fs/smb/server/vfs_cache.h b/fs/smb/server/vfs_cache.h
index 5cac022b540b..502efb16f05f 100644
--- a/fs/smb/server/vfs_cache.h
+++ b/fs/smb/server/vfs_cache.h
@@ -149,6 +149,10 @@ struct ksmbd_file {
 	bool				is_durable;
 	bool				is_persistent;
 	bool				is_resilient;
+	bool				has_app_instance_id;
+	bool				app_instance_version_valid;
+	u64				app_instance_version_high;
+	u64				app_instance_version_low;
 	bool				durable_reconnect_disabled;
 	bool				durable_replay_consumed;
 
@@ -209,6 +213,7 @@ void ksmbd_put_durable_fd(struct ksmbd_file *fp);
 int ksmbd_invalidate_durable_fd(unsigned long long id);
 bool ksmbd_has_other_active_fd(struct ksmbd_file *fp);
 bool ksmbd_has_stream_without_delete_share(struct ksmbd_file *fp);
+struct ksmbd_file *ksmbd_lookup_fd_app_instance_id(char *app_instance_id);
 int ksmbd_close_fd_app_instance_id(char *app_instance_id);
 struct ksmbd_file *ksmbd_lookup_fd_cguid(char *cguid);
 struct ksmbd_file *ksmbd_lookup_fd_inode(struct dentry *dentry);
-- 
2.25.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.