]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ksmbd: fix outstanding credit leak on abort and error paths
authorNamjae Jeon <linkinjeon@kernel.org>
Fri, 26 Jun 2026 01:52:22 +0000 (10:52 +0900)
committerSteve French <stfrench@microsoft.com>
Wed, 1 Jul 2026 02:29:46 +0000 (21:29 -0500)
smb2_validate_credit_charge() adds the request's CreditCharge to
conn->outstanding_credits when an SMB2 PDU is received, and
smb2_set_rsp_credits() subtracts it again when the response is built.
However smb2_set_rsp_credits() only runs on the normal response path:

  - __process_request() returning SERVER_HANDLER_ABORT (unimplemented
    command, command index out of range, signature check failure, or a
    handler that sets send_no_response such as a cancelled blocking
    lock) breaks out of the processing loop before set_rsp_credits() is
    called;
  - smb2_set_rsp_credits() itself returns early with -EINVAL (total
    credit overflow or insufficient credits) before the subtraction.

On all of these paths the charge added at receive time is never
returned, so conn->outstanding_credits only grows. Because a client can
repeatedly trigger them (e.g. by sending unimplemented commands or by
issuing and cancelling blocking locks), outstanding_credits eventually
reaches total_credits and smb2_validate_credit_charge() then rejects
every subsequent request, wedging the connection.

Record the charge that was added in work->credit_charge and release any
charge still pending at the single send. exit point of
__handle_ksmbd_work(), which all abort and error paths fall through to.
smb2_set_rsp_credits() clears work->credit_charge once it has returned
the charge so the response path is unchanged and the credit is never
released twice. Paths that never charged a credit (no multi-credit
support, validation failure) leave work->credit_charge at zero and are
unaffected.

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

index df0554a2c50d987974d1362dd619ac078c8cb924..88104f0cf3636c06c8b141f8e1da872cd164edcf 100644 (file)
@@ -67,6 +67,13 @@ struct ksmbd_work {
        /* Number of granted credits */
        unsigned int                    credits_granted;
 
+       /*
+        * Credit charge added to conn->outstanding_credits at receive time
+        * for the SMB2 PDU currently being processed, pending release.  Zero
+        * once the charge has been returned (on the response or error path).
+        */
+       unsigned short                  credit_charge;
+
        /* response smb header size */
        unsigned int                    response_sz;
 
index 36feda7e094269381f8cbefa3df485258e06a5bb..36a5ea4828ad699328e5a4737c483ce0e96a9a49 100644 (file)
@@ -242,6 +242,20 @@ static void __handle_ksmbd_work(struct ksmbd_work *work,
        } while (is_chained == true);
 
 send:
+       /*
+        * Release any credit charge still outstanding for this request.  On
+        * the normal path smb2_set_rsp_credits() already returned it, but the
+        * abort, error and send-no-response paths skip that call, so the
+        * charge would otherwise leak and eventually exhaust the connection's
+        * outstanding credit window.
+        */
+       if (work->credit_charge) {
+               spin_lock(&conn->credits_lock);
+               conn->outstanding_credits -= work->credit_charge;
+               work->credit_charge = 0;
+               spin_unlock(&conn->credits_lock);
+       }
+
        if (work->tcon)
                ksmbd_tree_connect_put(work->tcon);
        smb3_preauth_hash_rsp(work);
index 35fba49d27d68019d4002622296fdf70b85dda64..c0c4edd092c2b5ca795b5cc2ff4496622c99e6e0 100644 (file)
@@ -301,9 +301,10 @@ static inline int smb2_ioctl_resp_len(struct smb2_ioctl_req *h)
                le32_to_cpu(h->MaxOutputResponse);
 }
 
-static int smb2_validate_credit_charge(struct ksmbd_conn *conn,
+static int smb2_validate_credit_charge(struct ksmbd_work *work,
                                       struct smb2_hdr *hdr)
 {
+       struct ksmbd_conn *conn = work->conn;
        unsigned int req_len = 0, expect_resp_len = 0, calc_credit_num, max_len;
        unsigned short credit_charge = le16_to_cpu(hdr->CreditCharge);
        void *__hdr = hdr;
@@ -361,8 +362,10 @@ static int smb2_validate_credit_charge(struct ksmbd_conn *conn,
                ksmbd_debug(SMB, "Limits exceeding the maximum allowable outstanding requests, given : %u, pending : %u\n",
                            credit_charge, conn->outstanding_credits);
                ret = 1;
-       } else
+       } else {
                conn->outstanding_credits += credit_charge;
+               work->credit_charge = credit_charge;
+       }
 
        spin_unlock(&conn->credits_lock);
 
@@ -465,7 +468,7 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work)
 
 validate_credit:
        if ((work->conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) &&
-           smb2_validate_credit_charge(work->conn, hdr))
+           smb2_validate_credit_charge(work, hdr))
                return 1;
 
        return 0;
index 73b3758f41eedffcc3f821748a11c3065b95e596..727ba86ede36e6c5dd068a316605ee05086560e5 100644 (file)
@@ -363,6 +363,7 @@ int smb2_set_rsp_credits(struct ksmbd_work *work)
 
        conn->total_credits -= credit_charge;
        conn->outstanding_credits -= credit_charge;
+       work->credit_charge = 0;
        credits_requested = max_t(unsigned short,
                                  le16_to_cpu(req_hdr->CreditRequest), 1);