ksmbd: fix outstanding credit leak on abort and error paths

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>
This commit is contained in:
Namjae Jeon 2026-06-26 10:52:22 +09:00 committed by Steve French
parent 284dc80ff5
commit 4a0b782661
4 changed files with 28 additions and 3 deletions

View 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;

View 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);

View 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;

View 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);