Commit c460e789 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag '5.15-rc6-ksmbd-fixes' of git://git.samba.org/ksmbd

Pull ksmbd fixes from Steve French:
 "Ten fixes for the ksmbd kernel server, for improved security and
  additional buffer overflow checks:

   - a security improvement to session establishment to reduce the
     possibility of dictionary attacks

   - fix to ensure that maximum i/o size negotiated in the protocol is
     not less than 64K and not more than 8MB to better match expected
     behavior

   - fix for crediting (flow control) important to properly verify that
     sufficient credits are available for the requested operation

   - seven additional buffer overflow, buffer validation checks"

* tag '5.15-rc6-ksmbd-fixes' of git://git.samba.org/ksmbd:
  ksmbd: add buffer validation in session setup
  ksmbd: throttle session setup failures to avoid dictionary attacks
  ksmbd: validate OutputBufferLength of QUERY_DIR, QUERY_INFO, IOCTL requests
  ksmbd: validate credit charge after validating SMB2 PDU body size
  ksmbd: add buffer validation for smb direct
  ksmbd: limit read/write/trans buffer size not to exceed 8MB
  ksmbd: validate compound response buffer
  ksmbd: fix potencial 32bit overflow from data area check in smb2_write
  ksmbd: improve credits management
  ksmbd: add validation in smb2_ioctl
parents 0f386a60 0d994cd4
...@@ -298,8 +298,8 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob, ...@@ -298,8 +298,8 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
int blob_len, struct ksmbd_session *sess) int blob_len, struct ksmbd_session *sess)
{ {
char *domain_name; char *domain_name;
unsigned int lm_off, nt_off; unsigned int nt_off, dn_off;
unsigned short nt_len; unsigned short nt_len, dn_len;
int ret; int ret;
if (blob_len < sizeof(struct authenticate_message)) { if (blob_len < sizeof(struct authenticate_message)) {
...@@ -314,15 +314,17 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob, ...@@ -314,15 +314,17 @@ int ksmbd_decode_ntlmssp_auth_blob(struct authenticate_message *authblob,
return -EINVAL; return -EINVAL;
} }
lm_off = le32_to_cpu(authblob->LmChallengeResponse.BufferOffset);
nt_off = le32_to_cpu(authblob->NtChallengeResponse.BufferOffset); nt_off = le32_to_cpu(authblob->NtChallengeResponse.BufferOffset);
nt_len = le16_to_cpu(authblob->NtChallengeResponse.Length); nt_len = le16_to_cpu(authblob->NtChallengeResponse.Length);
dn_off = le32_to_cpu(authblob->DomainName.BufferOffset);
dn_len = le16_to_cpu(authblob->DomainName.Length);
if (blob_len < (u64)dn_off + dn_len || blob_len < (u64)nt_off + nt_len)
return -EINVAL;
/* TODO : use domain name that imported from configuration file */ /* TODO : use domain name that imported from configuration file */
domain_name = smb_strndup_from_utf16((const char *)authblob + domain_name = smb_strndup_from_utf16((const char *)authblob + dn_off,
le32_to_cpu(authblob->DomainName.BufferOffset), dn_len, true, sess->conn->local_nls);
le16_to_cpu(authblob->DomainName.Length), true,
sess->conn->local_nls);
if (IS_ERR(domain_name)) if (IS_ERR(domain_name))
return PTR_ERR(domain_name); return PTR_ERR(domain_name);
......
...@@ -61,6 +61,8 @@ struct ksmbd_conn *ksmbd_conn_alloc(void) ...@@ -61,6 +61,8 @@ struct ksmbd_conn *ksmbd_conn_alloc(void)
conn->local_nls = load_nls_default(); conn->local_nls = load_nls_default();
atomic_set(&conn->req_running, 0); atomic_set(&conn->req_running, 0);
atomic_set(&conn->r_count, 0); atomic_set(&conn->r_count, 0);
conn->total_credits = 1;
init_waitqueue_head(&conn->req_running_q); init_waitqueue_head(&conn->req_running_q);
INIT_LIST_HEAD(&conn->conns_list); INIT_LIST_HEAD(&conn->conns_list);
INIT_LIST_HEAD(&conn->sessions); INIT_LIST_HEAD(&conn->sessions);
......
...@@ -211,6 +211,7 @@ struct ksmbd_tree_disconnect_request { ...@@ -211,6 +211,7 @@ struct ksmbd_tree_disconnect_request {
*/ */
struct ksmbd_logout_request { struct ksmbd_logout_request {
__s8 account[KSMBD_REQ_MAX_ACCOUNT_NAME_SZ]; /* user account name */ __s8 account[KSMBD_REQ_MAX_ACCOUNT_NAME_SZ]; /* user account name */
__u32 account_flags;
}; };
/* /*
...@@ -317,6 +318,7 @@ enum KSMBD_TREE_CONN_STATUS { ...@@ -317,6 +318,7 @@ enum KSMBD_TREE_CONN_STATUS {
#define KSMBD_USER_FLAG_BAD_UID BIT(2) #define KSMBD_USER_FLAG_BAD_UID BIT(2)
#define KSMBD_USER_FLAG_BAD_USER BIT(3) #define KSMBD_USER_FLAG_BAD_USER BIT(3)
#define KSMBD_USER_FLAG_GUEST_ACCOUNT BIT(4) #define KSMBD_USER_FLAG_GUEST_ACCOUNT BIT(4)
#define KSMBD_USER_FLAG_DELAY_SESSION BIT(5)
/* /*
* Share config flags. * Share config flags.
......
...@@ -55,7 +55,7 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp) ...@@ -55,7 +55,7 @@ struct ksmbd_user *ksmbd_alloc_user(struct ksmbd_login_response *resp)
void ksmbd_free_user(struct ksmbd_user *user) void ksmbd_free_user(struct ksmbd_user *user)
{ {
ksmbd_ipc_logout_request(user->name); ksmbd_ipc_logout_request(user->name, user->flags);
kfree(user->name); kfree(user->name);
kfree(user->passkey); kfree(user->passkey);
kfree(user); kfree(user);
......
...@@ -18,6 +18,7 @@ struct ksmbd_user { ...@@ -18,6 +18,7 @@ struct ksmbd_user {
size_t passkey_sz; size_t passkey_sz;
char *passkey; char *passkey;
unsigned int failed_login_count;
}; };
static inline bool user_guest(struct ksmbd_user *user) static inline bool user_guest(struct ksmbd_user *user)
......
...@@ -284,11 +284,13 @@ static inline int smb2_ioctl_resp_len(struct smb2_ioctl_req *h) ...@@ -284,11 +284,13 @@ static inline int smb2_ioctl_resp_len(struct smb2_ioctl_req *h)
le32_to_cpu(h->MaxOutputResponse); le32_to_cpu(h->MaxOutputResponse);
} }
static int smb2_validate_credit_charge(struct smb2_hdr *hdr) static int smb2_validate_credit_charge(struct ksmbd_conn *conn,
struct smb2_hdr *hdr)
{ {
int req_len = 0, expect_resp_len = 0, calc_credit_num, max_len; unsigned int req_len = 0, expect_resp_len = 0, calc_credit_num, max_len;
int credit_charge = le16_to_cpu(hdr->CreditCharge); unsigned short credit_charge = le16_to_cpu(hdr->CreditCharge);
void *__hdr = hdr; void *__hdr = hdr;
int ret;
switch (hdr->Command) { switch (hdr->Command) {
case SMB2_QUERY_INFO: case SMB2_QUERY_INFO:
...@@ -310,21 +312,37 @@ static int smb2_validate_credit_charge(struct smb2_hdr *hdr) ...@@ -310,21 +312,37 @@ static int smb2_validate_credit_charge(struct smb2_hdr *hdr)
req_len = smb2_ioctl_req_len(__hdr); req_len = smb2_ioctl_req_len(__hdr);
expect_resp_len = smb2_ioctl_resp_len(__hdr); expect_resp_len = smb2_ioctl_resp_len(__hdr);
break; break;
default: case SMB2_CANCEL:
return 0; return 0;
default:
req_len = 1;
break;
} }
credit_charge = max(1, credit_charge); credit_charge = max_t(unsigned short, credit_charge, 1);
max_len = max(req_len, expect_resp_len); max_len = max_t(unsigned int, req_len, expect_resp_len);
calc_credit_num = DIV_ROUND_UP(max_len, SMB2_MAX_BUFFER_SIZE); calc_credit_num = DIV_ROUND_UP(max_len, SMB2_MAX_BUFFER_SIZE);
if (credit_charge < calc_credit_num) { if (credit_charge < calc_credit_num) {
pr_err("Insufficient credit charge, given: %d, needed: %d\n", ksmbd_debug(SMB, "Insufficient credit charge, given: %d, needed: %d\n",
credit_charge, calc_credit_num); credit_charge, calc_credit_num);
return 1;
} else if (credit_charge > conn->max_credits) {
ksmbd_debug(SMB, "Too large credit charge: %d\n", credit_charge);
return 1; return 1;
} }
return 0; spin_lock(&conn->credits_lock);
if (credit_charge <= conn->total_credits) {
conn->total_credits -= credit_charge;
ret = 0;
} else {
ksmbd_debug(SMB, "Insufficient credits granted, given: %u, granted: %u\n",
credit_charge, conn->total_credits);
ret = 1;
}
spin_unlock(&conn->credits_lock);
return ret;
} }
int ksmbd_smb2_check_message(struct ksmbd_work *work) int ksmbd_smb2_check_message(struct ksmbd_work *work)
...@@ -382,26 +400,20 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work) ...@@ -382,26 +400,20 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work)
} }
} }
if ((work->conn->vals->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) &&
smb2_validate_credit_charge(hdr)) {
work->conn->ops->set_rsp_status(work, STATUS_INVALID_PARAMETER);
return 1;
}
if (smb2_calc_size(hdr, &clc_len)) if (smb2_calc_size(hdr, &clc_len))
return 1; return 1;
if (len != clc_len) { if (len != clc_len) {
/* client can return one byte more due to implied bcc[0] */ /* client can return one byte more due to implied bcc[0] */
if (clc_len == len + 1) if (clc_len == len + 1)
return 0; goto validate_credit;
/* /*
* Some windows servers (win2016) will pad also the final * Some windows servers (win2016) will pad also the final
* PDU in a compound to 8 bytes. * PDU in a compound to 8 bytes.
*/ */
if (ALIGN(clc_len, 8) == len) if (ALIGN(clc_len, 8) == len)
return 0; goto validate_credit;
/* /*
* windows client also pad up to 8 bytes when compounding. * windows client also pad up to 8 bytes when compounding.
...@@ -414,7 +426,7 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work) ...@@ -414,7 +426,7 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work)
"cli req padded more than expected. Length %d not %d for cmd:%d mid:%llu\n", "cli req padded more than expected. Length %d not %d for cmd:%d mid:%llu\n",
len, clc_len, command, len, clc_len, command,
le64_to_cpu(hdr->MessageId)); le64_to_cpu(hdr->MessageId));
return 0; goto validate_credit;
} }
ksmbd_debug(SMB, ksmbd_debug(SMB,
...@@ -425,6 +437,13 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work) ...@@ -425,6 +437,13 @@ int ksmbd_smb2_check_message(struct ksmbd_work *work)
return 1; return 1;
} }
validate_credit:
if ((work->conn->vals->capabilities & SMB2_GLOBAL_CAP_LARGE_MTU) &&
smb2_validate_credit_charge(work->conn, hdr)) {
work->conn->ops->set_rsp_status(work, STATUS_INVALID_PARAMETER);
return 1;
}
return 0; return 0;
} }
......
...@@ -284,6 +284,7 @@ int init_smb3_11_server(struct ksmbd_conn *conn) ...@@ -284,6 +284,7 @@ int init_smb3_11_server(struct ksmbd_conn *conn)
void init_smb2_max_read_size(unsigned int sz) void init_smb2_max_read_size(unsigned int sz)
{ {
sz = clamp_val(sz, SMB3_MIN_IOSIZE, SMB3_MAX_IOSIZE);
smb21_server_values.max_read_size = sz; smb21_server_values.max_read_size = sz;
smb30_server_values.max_read_size = sz; smb30_server_values.max_read_size = sz;
smb302_server_values.max_read_size = sz; smb302_server_values.max_read_size = sz;
...@@ -292,6 +293,7 @@ void init_smb2_max_read_size(unsigned int sz) ...@@ -292,6 +293,7 @@ void init_smb2_max_read_size(unsigned int sz)
void init_smb2_max_write_size(unsigned int sz) void init_smb2_max_write_size(unsigned int sz)
{ {
sz = clamp_val(sz, SMB3_MIN_IOSIZE, SMB3_MAX_IOSIZE);
smb21_server_values.max_write_size = sz; smb21_server_values.max_write_size = sz;
smb30_server_values.max_write_size = sz; smb30_server_values.max_write_size = sz;
smb302_server_values.max_write_size = sz; smb302_server_values.max_write_size = sz;
...@@ -300,6 +302,7 @@ void init_smb2_max_write_size(unsigned int sz) ...@@ -300,6 +302,7 @@ void init_smb2_max_write_size(unsigned int sz)
void init_smb2_max_trans_size(unsigned int sz) void init_smb2_max_trans_size(unsigned int sz)
{ {
sz = clamp_val(sz, SMB3_MIN_IOSIZE, SMB3_MAX_IOSIZE);
smb21_server_values.max_trans_size = sz; smb21_server_values.max_trans_size = sz;
smb30_server_values.max_trans_size = sz; smb30_server_values.max_trans_size = sz;
smb302_server_values.max_trans_size = sz; smb302_server_values.max_trans_size = sz;
......
This diff is collapsed.
...@@ -113,6 +113,8 @@ ...@@ -113,6 +113,8 @@
#define SMB21_DEFAULT_IOSIZE (1024 * 1024) #define SMB21_DEFAULT_IOSIZE (1024 * 1024)
#define SMB3_DEFAULT_IOSIZE (4 * 1024 * 1024) #define SMB3_DEFAULT_IOSIZE (4 * 1024 * 1024)
#define SMB3_DEFAULT_TRANS_SIZE (1024 * 1024) #define SMB3_DEFAULT_TRANS_SIZE (1024 * 1024)
#define SMB3_MIN_IOSIZE (64 * 1024)
#define SMB3_MAX_IOSIZE (8 * 1024 * 1024)
/* /*
* SMB2 Header Definition * SMB2 Header Definition
......
...@@ -601,7 +601,7 @@ int ksmbd_ipc_tree_disconnect_request(unsigned long long session_id, ...@@ -601,7 +601,7 @@ int ksmbd_ipc_tree_disconnect_request(unsigned long long session_id,
return ret; return ret;
} }
int ksmbd_ipc_logout_request(const char *account) int ksmbd_ipc_logout_request(const char *account, int flags)
{ {
struct ksmbd_ipc_msg *msg; struct ksmbd_ipc_msg *msg;
struct ksmbd_logout_request *req; struct ksmbd_logout_request *req;
...@@ -616,6 +616,7 @@ int ksmbd_ipc_logout_request(const char *account) ...@@ -616,6 +616,7 @@ int ksmbd_ipc_logout_request(const char *account)
msg->type = KSMBD_EVENT_LOGOUT_REQUEST; msg->type = KSMBD_EVENT_LOGOUT_REQUEST;
req = (struct ksmbd_logout_request *)msg->payload; req = (struct ksmbd_logout_request *)msg->payload;
req->account_flags = flags;
strscpy(req->account, account, KSMBD_REQ_MAX_ACCOUNT_NAME_SZ); strscpy(req->account, account, KSMBD_REQ_MAX_ACCOUNT_NAME_SZ);
ret = ipc_msg_send(msg); ret = ipc_msg_send(msg);
......
...@@ -25,7 +25,7 @@ ksmbd_ipc_tree_connect_request(struct ksmbd_session *sess, ...@@ -25,7 +25,7 @@ ksmbd_ipc_tree_connect_request(struct ksmbd_session *sess,
struct sockaddr *peer_addr); struct sockaddr *peer_addr);
int ksmbd_ipc_tree_disconnect_request(unsigned long long session_id, int ksmbd_ipc_tree_disconnect_request(unsigned long long session_id,
unsigned long long connect_id); unsigned long long connect_id);
int ksmbd_ipc_logout_request(const char *account); int ksmbd_ipc_logout_request(const char *account, int flags);
struct ksmbd_share_config_response * struct ksmbd_share_config_response *
ksmbd_ipc_share_config_request(const char *name); ksmbd_ipc_share_config_request(const char *name);
struct ksmbd_spnego_authen_response * struct ksmbd_spnego_authen_response *
......
...@@ -549,6 +549,10 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc) ...@@ -549,6 +549,10 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc)
switch (recvmsg->type) { switch (recvmsg->type) {
case SMB_DIRECT_MSG_NEGOTIATE_REQ: case SMB_DIRECT_MSG_NEGOTIATE_REQ:
if (wc->byte_len < sizeof(struct smb_direct_negotiate_req)) {
put_empty_recvmsg(t, recvmsg);
return;
}
t->negotiation_requested = true; t->negotiation_requested = true;
t->full_packet_received = true; t->full_packet_received = true;
wake_up_interruptible(&t->wait_status); wake_up_interruptible(&t->wait_status);
...@@ -556,10 +560,23 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc) ...@@ -556,10 +560,23 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc)
case SMB_DIRECT_MSG_DATA_TRANSFER: { case SMB_DIRECT_MSG_DATA_TRANSFER: {
struct smb_direct_data_transfer *data_transfer = struct smb_direct_data_transfer *data_transfer =
(struct smb_direct_data_transfer *)recvmsg->packet; (struct smb_direct_data_transfer *)recvmsg->packet;
int data_length = le32_to_cpu(data_transfer->data_length); unsigned int data_length;
int avail_recvmsg_count, receive_credits; int avail_recvmsg_count, receive_credits;
if (wc->byte_len <
offsetof(struct smb_direct_data_transfer, padding)) {
put_empty_recvmsg(t, recvmsg);
return;
}
data_length = le32_to_cpu(data_transfer->data_length);
if (data_length) { if (data_length) {
if (wc->byte_len < sizeof(struct smb_direct_data_transfer) +
(u64)data_length) {
put_empty_recvmsg(t, recvmsg);
return;
}
if (t->full_packet_received) if (t->full_packet_received)
recvmsg->first_segment = true; recvmsg->first_segment = true;
...@@ -568,7 +585,7 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc) ...@@ -568,7 +585,7 @@ static void recv_done(struct ib_cq *cq, struct ib_wc *wc)
else else
t->full_packet_received = true; t->full_packet_received = true;
enqueue_reassembly(t, recvmsg, data_length); enqueue_reassembly(t, recvmsg, (int)data_length);
wake_up_interruptible(&t->wait_reassembly_queue); wake_up_interruptible(&t->wait_reassembly_queue);
spin_lock(&t->receive_credit_lock); spin_lock(&t->receive_credit_lock);
......
...@@ -1023,7 +1023,7 @@ int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp, ...@@ -1023,7 +1023,7 @@ int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp,
int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length, int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length,
struct file_allocated_range_buffer *ranges, struct file_allocated_range_buffer *ranges,
int in_count, int *out_count) unsigned int in_count, unsigned int *out_count)
{ {
struct file *f = fp->filp; struct file *f = fp->filp;
struct inode *inode = file_inode(fp->filp); struct inode *inode = file_inode(fp->filp);
......
...@@ -166,7 +166,7 @@ int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp, ...@@ -166,7 +166,7 @@ int ksmbd_vfs_zero_data(struct ksmbd_work *work, struct ksmbd_file *fp,
struct file_allocated_range_buffer; struct file_allocated_range_buffer;
int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length, int ksmbd_vfs_fqar_lseek(struct ksmbd_file *fp, loff_t start, loff_t length,
struct file_allocated_range_buffer *ranges, struct file_allocated_range_buffer *ranges,
int in_count, int *out_count); unsigned int in_count, unsigned int *out_count);
int ksmbd_vfs_unlink(struct user_namespace *user_ns, int ksmbd_vfs_unlink(struct user_namespace *user_ns,
struct dentry *dir, struct dentry *dentry); struct dentry *dir, struct dentry *dentry);
void *ksmbd_vfs_init_kstat(char **p, struct ksmbd_kstat *ksmbd_kstat); void *ksmbd_vfs_init_kstat(char **p, struct ksmbd_kstat *ksmbd_kstat);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment