]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ksmbd: validate SMB2 lease create contexts
authorNamjae Jeon <linkinjeon@kernel.org>
Thu, 18 Jun 2026 01:32:44 +0000 (10:32 +0900)
committerSteve French <stfrench@microsoft.com>
Tue, 23 Jun 2026 01:15:03 +0000 (20:15 -0500)
Validate SMB2 lease context lengths, requested lease state bits, and v2
flags before using the context. Return errors via ERR_PTR so CREATE can
distinguish a missing lease context from a malformed one.

Also ignore lease v2 contexts for SMB 2.1, where they are not valid.

Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
Signed-off-by: Steve French <stfrench@microsoft.com>
fs/smb/server/oplock.c
fs/smb/server/smb2pdu.c

index 60e7e821c2455db2f073b1c8bc2089d246f16639..5c6c0550a477354e755c34e33dba1a7d5a501b76 100644 (file)
@@ -5,6 +5,7 @@
  */
 
 #include <linux/moduleparam.h>
+#include <linux/err.h>
 
 #include "glob.h"
 #include "oplock.h"
 static LIST_HEAD(lease_table_list);
 static DEFINE_RWLOCK(lease_list_lock);
 
+#define SMB2_LEASE_STATE_MASK_LE       (SMB2_LEASE_READ_CACHING_LE | \
+                                        SMB2_LEASE_HANDLE_CACHING_LE | \
+                                        SMB2_LEASE_WRITE_CACHING_LE)
+
+static bool lease_state_valid(__le32 state)
+{
+       return !(state & ~SMB2_LEASE_STATE_MASK_LE);
+}
+
+static bool lease_v2_flags_valid(__le32 flags)
+{
+       return !(flags & ~SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE);
+}
+
 /**
  * alloc_opinfo() - allocate a new opinfo object for oplock info
  * @work:      smb work
@@ -1537,12 +1552,14 @@ struct lease_ctx_info *parse_lease_state(void *open_req)
        struct lease_ctx_info *lreq;
 
        cc = smb2_find_context_vals(req, SMB2_CREATE_REQUEST_LEASE, 4);
-       if (IS_ERR_OR_NULL(cc))
+       if (IS_ERR(cc))
+               return ERR_CAST(cc);
+       if (!cc)
                return NULL;
 
        lreq = kzalloc_obj(struct lease_ctx_info, KSMBD_DEFAULT_GFP);
        if (!lreq)
-               return NULL;
+               return ERR_PTR(-ENOMEM);
 
        if (sizeof(struct lease_context_v2) == le32_to_cpu(cc->DataLength)) {
                struct create_lease_v2 *lc = (struct create_lease_v2 *)cc;
@@ -1556,11 +1573,14 @@ struct lease_ctx_info *parse_lease_state(void *open_req)
                lreq->flags = lc->lcontext.LeaseFlags;
                lreq->epoch = lc->lcontext.Epoch;
                lreq->duration = lc->lcontext.LeaseDuration;
+               if (!lease_state_valid(lreq->req_state) ||
+                   !lease_v2_flags_valid(lreq->flags))
+                       goto err_out;
                if (lreq->flags == SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET_LE)
                        memcpy(lreq->parent_lease_key, lc->lcontext.ParentLeaseKey,
                               SMB2_LEASE_KEY_SIZE);
                lreq->version = 2;
-       } else {
+       } else if (sizeof(struct lease_context) == le32_to_cpu(cc->DataLength)) {
                struct create_lease *lc = (struct create_lease *)cc;
 
                if (le16_to_cpu(cc->DataOffset) + le32_to_cpu(cc->DataLength) <
@@ -1571,12 +1591,15 @@ struct lease_ctx_info *parse_lease_state(void *open_req)
                lreq->req_state = lc->lcontext.LeaseState;
                lreq->flags = lc->lcontext.LeaseFlags;
                lreq->duration = lc->lcontext.LeaseDuration;
+               if (!lease_state_valid(lreq->req_state))
+                       goto err_out;
                lreq->version = 1;
-       }
+       } else
+               goto err_out;
        return lreq;
 err_out:
        kfree(lreq);
-       return NULL;
+       return ERR_PTR(-EINVAL);
 }
 
 /**
index 95c7b09c67437a706940fd24629ba31a3007f83c..19e819898fd0ff8873b39e78561e6bbf398a75c6 100644 (file)
@@ -3108,6 +3108,17 @@ int smb2_open(struct ksmbd_work *work)
        if (server_conf.flags & KSMBD_GLOBAL_FLAG_DURABLE_HANDLE &&
            req->CreateContextsOffset) {
                lc = parse_lease_state(req);
+               if (IS_ERR(lc)) {
+                       rc = PTR_ERR(lc);
+                       lc = NULL;
+                       goto err_out2;
+               }
+               if (lc && lc->version == 2 && conn->dialect < SMB30_PROT_ID) {
+                       kfree(lc);
+                       lc = NULL;
+                       if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE)
+                               req_op_level = SMB2_OPLOCK_LEVEL_NONE;
+               }
                rc = parse_durable_handle_context(work, req, lc, &dh_info);
                if (rc) {
                        ksmbd_debug(SMB, "error parsing durable handle context\n");
@@ -3139,8 +3150,19 @@ int smb2_open(struct ksmbd_work *work)
 
                        goto reconnected_fp;
                }
-       } else if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE)
+       } else if (req_op_level == SMB2_OPLOCK_LEVEL_LEASE) {
                lc = parse_lease_state(req);
+               if (IS_ERR(lc)) {
+                       rc = PTR_ERR(lc);
+                       lc = NULL;
+                       goto err_out2;
+               }
+               if (lc && lc->version == 2 && conn->dialect < SMB30_PROT_ID) {
+                       kfree(lc);
+                       lc = NULL;
+                       req_op_level = SMB2_OPLOCK_LEVEL_NONE;
+               }
+       }
 
        if (le32_to_cpu(req->ImpersonationLevel) > le32_to_cpu(IL_DELEGATE)) {
                pr_err("Invalid impersonationlevel : 0x%x\n",