mirror of
https://github.com/torvalds/linux.git
synced 2026-10-08 11:36:02 +02:00
ksmbd: implement the command sequence window
ksmbd tracked only credit counts (total_credits/outstanding_credits) and
never validated the MessageId of an incoming request. As a result a
request carrying a MessageId outside the granted range was accepted, a
MessageId could be replayed, and a 64-bit sequence wrap was not detected.
Maintain a command sequence window per connection:
- [seq_low, seq_high) is the range of granted sequence numbers and
seq_bitmap records which of them have been granted but not yet
consumed. The window starts as { 0 } at connection setup.
- smb2_set_rsp_credits() extends seq_high by the number of credits it
grants (setting the corresponding bits), capped so the window never
spans more than KSMBD_CMD_SEQ_WINDOW (== SMB2_MAX_CREDITS) sequence
numbers. This implements the "limit the range of acceptable
sequence numbers" allowance and keeps seq_bitmap usable as a ring.
- smb2_check_sequence_number(), run for every SMB2 request from
ksmbd_smb2_check_message(), verifies that the CreditCharge
consecutive sequence numbers starting at MessageId lie within the
window and have not already been consumed, then removes them and
slides seq_low forward. CANCEL consumes nothing. A violation
(out of window, replay, or wrap) tears the connection down.
The legacy SMB1 multi-protocol negotiate occupies sequence number 0 but
does not pass through ksmbd_smb2_check_message(), so it consumes that
sequence number explicitly; otherwise seq_low would stay pinned at 0
after the upgrade to SMB2 and eventually stall credit grants.
For an in-order client seq_high - seq_low equals total_credits, so the
window-room cap never reduces the number of credits granted. it only
engages for a client that withholds low sequence numbers.
init_smb2_max_credits() now clamps the configured maximum to
SMB2_MAX_CREDITS so the window (and its bitmap) can always represent
every outstanding sequence number.
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
parent
7b46161088
commit
fd8c97d7c1
|
|
@ -291,6 +291,14 @@ struct ksmbd_conn *ksmbd_conn_alloc(void)
|
|||
conn->total_credits = 1;
|
||||
conn->outstanding_credits = 0;
|
||||
|
||||
/*
|
||||
* The command sequence window starts as the set { 0 } when the
|
||||
* connection is established.
|
||||
*/
|
||||
conn->seq_low = 0;
|
||||
conn->seq_high = 1;
|
||||
__set_bit(0, conn->seq_bitmap);
|
||||
|
||||
init_waitqueue_head(&conn->req_running_q);
|
||||
init_waitqueue_head(&conn->r_count_q);
|
||||
INIT_LIST_HEAD(&conn->requests);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
#include <linux/nls.h>
|
||||
#include <linux/unicode.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/bitmap.h>
|
||||
|
||||
#include "smb_common.h"
|
||||
#include "ksmbd_work.h"
|
||||
|
|
@ -25,6 +26,15 @@ struct smbdirect_buffer_descriptor_v1;
|
|||
|
||||
#define KSMBD_SOCKET_BACKLOG 16
|
||||
|
||||
/*
|
||||
* Size of the per-connection SMB2 command sequence window. This mirrors
|
||||
* SMB2_MAX_CREDITS, the maximum number of credits (and therefore the
|
||||
* maximum number of outstanding sequence numbers) that can be granted on
|
||||
* a connection. It must be a power of two so the window can be indexed as
|
||||
* a ring.
|
||||
*/
|
||||
#define KSMBD_CMD_SEQ_WINDOW 8192
|
||||
|
||||
enum {
|
||||
KSMBD_SESS_NEW = 0,
|
||||
KSMBD_SESS_GOOD,
|
||||
|
|
@ -74,6 +84,16 @@ struct ksmbd_conn {
|
|||
unsigned int total_credits;
|
||||
unsigned int outstanding_credits;
|
||||
spinlock_t credits_lock;
|
||||
/*
|
||||
* Connection command sequence window. [seq_low, seq_high) is the
|
||||
* range of granted sequence numbers (message IDs). seq_bitmap marks
|
||||
* the ones in that range that have been granted but
|
||||
* not yet consumed by a received request. All three are protected by
|
||||
* credits_lock.
|
||||
*/
|
||||
u64 seq_low;
|
||||
u64 seq_high;
|
||||
DECLARE_BITMAP(seq_bitmap, KSMBD_CMD_SEQ_WINDOW);
|
||||
wait_queue_head_t req_running_q;
|
||||
wait_queue_head_t r_count_q;
|
||||
/* Lock to protect requests list*/
|
||||
|
|
|
|||
|
|
@ -372,6 +372,75 @@ static int smb2_validate_credit_charge(struct ksmbd_work *work,
|
|||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that the sequence number(s) consumed by an incoming request fall
|
||||
* within the connection's command sequence window and are not a replay, then
|
||||
* remove them from the window. Returns 0 if the request
|
||||
* may proceed, or 1 if it is invalid and the connection must be torn down.
|
||||
*/
|
||||
static int smb2_check_sequence_number(struct ksmbd_work *work,
|
||||
struct smb2_hdr *hdr)
|
||||
{
|
||||
struct ksmbd_conn *conn = work->conn;
|
||||
u64 mid = le64_to_cpu(hdr->MessageId);
|
||||
unsigned short charge;
|
||||
u64 i;
|
||||
int ret = 0;
|
||||
|
||||
/* An SMB2 CANCEL consumes no sequence number. */
|
||||
if (hdr->Command == SMB2_CANCEL)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* A multi-credit request consumes CreditCharge consecutive sequence
|
||||
* numbers; every other request consumes exactly one.
|
||||
*/
|
||||
charge = le16_to_cpu(hdr->CreditCharge);
|
||||
if (!(conn->vals->req_capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) ||
|
||||
charge == 0)
|
||||
charge = 1;
|
||||
|
||||
/* The 64-bit sequence number space must not wrap. */
|
||||
if (mid + charge < mid) {
|
||||
pr_err("SMB2 sequence number wrapped (mid %llu charge %u)\n",
|
||||
mid, charge);
|
||||
return 1;
|
||||
}
|
||||
|
||||
spin_lock(&conn->credits_lock);
|
||||
|
||||
/* The whole range must lie within the granted window... */
|
||||
if (mid < conn->seq_low || mid + charge > conn->seq_high) {
|
||||
ksmbd_debug(SMB,
|
||||
"MessageId %llu (charge %u) outside command window [%llu, %llu)\n",
|
||||
mid, charge, conn->seq_low, conn->seq_high);
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* ...and none of it may have been consumed already (replay). */
|
||||
for (i = mid; i < mid + charge; i++) {
|
||||
if (!test_bit(i & (KSMBD_CMD_SEQ_WINDOW - 1), conn->seq_bitmap)) {
|
||||
ksmbd_debug(SMB,
|
||||
"replayed sequence number %llu (mid %llu charge %u)\n",
|
||||
i, mid, charge);
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Consume the sequence numbers and slide the low edge forward. */
|
||||
for (i = mid; i < mid + charge; i++)
|
||||
__clear_bit(i & (KSMBD_CMD_SEQ_WINDOW - 1), conn->seq_bitmap);
|
||||
while (conn->seq_low < conn->seq_high &&
|
||||
!test_bit(conn->seq_low & (KSMBD_CMD_SEQ_WINDOW - 1),
|
||||
conn->seq_bitmap))
|
||||
conn->seq_low++;
|
||||
out:
|
||||
spin_unlock(&conn->credits_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ksmbd_smb2_check_message(struct ksmbd_work *work)
|
||||
{
|
||||
struct smb2_pdu *pdu = ksmbd_req_buf_next(work);
|
||||
|
|
@ -476,6 +545,16 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work)
|
|||
smb2_validate_credit_charge(work, hdr))
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* A sequence number violation (out of window or a replay) is a
|
||||
* protocol error. tear the connection down rather than
|
||||
* keep accepting requests on it.
|
||||
*/
|
||||
if (smb2_check_sequence_number(work, hdr)) {
|
||||
ksmbd_conn_set_exiting(work->conn);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -331,6 +331,13 @@ void init_smb2_max_trans_size(unsigned int sz)
|
|||
|
||||
void init_smb2_max_credits(unsigned int sz)
|
||||
{
|
||||
/*
|
||||
* The command sequence window (and its backing bitmap) can track at
|
||||
* most SMB2_MAX_CREDITS outstanding sequence numbers, so the number of
|
||||
* credits granted on a connection must not exceed that.
|
||||
*/
|
||||
if (sz > SMB2_MAX_CREDITS)
|
||||
sz = SMB2_MAX_CREDITS;
|
||||
smb21_server_values.max_credits = sz;
|
||||
smb30_server_values.max_credits = sz;
|
||||
smb302_server_values.max_credits = sz;
|
||||
|
|
|
|||
|
|
@ -388,6 +388,7 @@ int smb2_set_rsp_credits(struct ksmbd_work *work)
|
|||
struct ksmbd_conn *conn = work->conn;
|
||||
unsigned short credits_requested, aux_max;
|
||||
unsigned short credit_charge, credits_granted = 0;
|
||||
u64 window_room, i;
|
||||
|
||||
if (work->send_no_response)
|
||||
return 0;
|
||||
|
|
@ -424,11 +425,26 @@ int smb2_set_rsp_credits(struct ksmbd_work *work)
|
|||
aux_max = 1;
|
||||
else
|
||||
aux_max = conn->vals->max_credits - conn->total_credits;
|
||||
|
||||
/*
|
||||
* The command sequence window must not grow beyond
|
||||
* KSMBD_CMD_SEQ_WINDOW sequence numbers ahead of the oldest one still
|
||||
* outstanding. Cap the grant by the room left in the window so that
|
||||
* credits are withheld until the client consumes the low end (and so
|
||||
* that seq_bitmap stays usable as a ring).
|
||||
*/
|
||||
window_room = conn->seq_low + KSMBD_CMD_SEQ_WINDOW - conn->seq_high;
|
||||
aux_max = min_t(unsigned short, aux_max, window_room);
|
||||
credits_granted = min_t(unsigned short, credits_requested, aux_max);
|
||||
|
||||
conn->total_credits += credits_granted;
|
||||
work->credits_granted += credits_granted;
|
||||
|
||||
/* Extend the sequence window to cover the newly granted credits. */
|
||||
for (i = conn->seq_high; i < conn->seq_high + credits_granted; i++)
|
||||
__set_bit(i & (KSMBD_CMD_SEQ_WINDOW - 1), conn->seq_bitmap);
|
||||
conn->seq_high += credits_granted;
|
||||
|
||||
if (!req_hdr->NextCommand) {
|
||||
/* Update CreditRequest in last request */
|
||||
hdr->CreditRequest = cpu_to_le16(work->credits_granted);
|
||||
|
|
|
|||
|
|
@ -164,7 +164,22 @@ int ksmbd_verify_smb_message(struct ksmbd_work *work)
|
|||
hdr = smb_get_msg(work->request_buf);
|
||||
if (*(__le32 *)hdr->Protocol == SMB1_PROTO_NUMBER &&
|
||||
hdr->Command == SMB_COM_NEGOTIATE) {
|
||||
work->conn->outstanding_credits++;
|
||||
struct ksmbd_conn *conn = work->conn;
|
||||
|
||||
conn->outstanding_credits++;
|
||||
/*
|
||||
* A legacy SMB1 multi-protocol negotiate occupies sequence
|
||||
* number 0 but does not pass through
|
||||
* ksmbd_smb2_check_message(). Consume it here so that, after
|
||||
* the connection is upgraded to SMB2, the command sequence
|
||||
* window can advance instead of staying pinned at 0.
|
||||
*/
|
||||
spin_lock(&conn->credits_lock);
|
||||
if (conn->seq_low == 0) {
|
||||
__clear_bit(0, conn->seq_bitmap);
|
||||
conn->seq_low = 1;
|
||||
}
|
||||
spin_unlock(&conn->credits_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user